/**
 * Store Ui Framework for jQuery v1.3.2<br>
 *
 * Copyright 2013, SK Planet<br>
 *
 * Licensed under the APACHE License
 *
 * @author nayasis@sk.com
 * @version 5.0
 * @since 2014.02.25
 */
var su = window.su || {};

/**
* 공통상수
*/
su.c = {

  /** CONTEXT ROOT */
  ROOT_PATH : env.contextPath,

  TOP_CODE  : env.topCode,
  LEFT_CODE : env.leftCode,
  
  /** byte 체크시 문자셋 기준 */
  CHECK_CHRSET : "UTF-8",
  
  /** 에러페이지 URL 경로 (세션중단) */
  URL_NO_SESSION : "/adminMember/adminLogin.omp",

  // 콤보
  COMBO : {
    LABEL_ALL    : "전체",
    LABEL_SELECT : "선택"
  },

  // 각종 ID 모음
  ID : {
    LOADING_BAR : "#_su_LoadingBar",
    WIN_FORM    : "#_su_winForm",
    WIN_TIP     : "#_su_tip",
    DEBUG_PANEL : "#_su_logPanel",
    DEBUG_TXT   : "#_su_logPanelDesc",
    FILE_DN     : "#_suDownload"
  },

  // 각종 CLASS 모음
  CLASS : {
    FOCUS   : "suFocus",
    INVALID : "suInvalid",
    TIP     : "suTip"
  },

  // 서버요청
  REQ : {
    TYPE_FORM : "kfrm",
    TYPE_JSON : "kajx",
    TYPE_XML  : "kxml",
    DATA_JSON : "jsonp"
  },

  // window
  WINDOW : {
    ID_SEQ      : "_autoGenId_",
    PARAM_TOKEN : "_metaToken"
  },

  // REDRAW 관련 공통상수
  REDRAW : {
    DIV_FOCUS : "_duRedrawFocus_"
  },

  // Date
  DATE : {
    FORMAT         : "YYYY-MM-DD", // 기본 날짜형식
    SERVER_DATE    : null,
    LOCAL_DATE     : null,
    PICKER_BTN_IMG : "/css/su/images/calender.gif"
  },

  // 정규식처리 관련 공통상수 (번역 대상에 포함되지 않음)
  REG_PTN : {

    TRIM           : /(^ *)|( *$)/g,
    LTRIM          : /(^ *)/g,
    RTRIM          : /( *$)/g,
    INNER_TRIM     : / +/g,
    INNER_SPACE    : / /g,
    NUM            : /[0-9]/g,
    NOT_NUMERIC    : /[^0-9\.]/g,
    NOT_NUM        : /[^0-9]/g,
    NOT_NUM_PARSE  : /[^0-9\.-]/g,
    ENG            : /[a-zA-Z]/g,
    NOT_ENG        : /[^a-zA-Z]/g,
    KOR            : /[ㄱ-ㅎㅏ-ㅣ가-힣]/g,
    NOT_KOR        : /[^ㄱ-ㅎㅏ-ㅣ가-힣]/g,
    NOT_NUM_OR_ENG : /[^0-9a-zA-Z]/g,
    MONEY          : /(\d)(?=(?:\d{3})+(?!\d))/g,

    HAS_HTML       : /<\/?[a-zA-Z][a-zA-Z0-9]*[^<>]*>/,
    IS_NUM         : /^[0-9]+$/,
    IS_NOT_NUM     : /^[^0-9]+$/,
    IS_NOT_KOR     : /^[^ㄱ-ㅎㅏ-ㅣ가-힣]+$/,
    IS_ENG         : /^[a-zA-Z]+$/,
    IS_NUM_ENG     : /^[0-9a-zA-Z]+$/,
    IS_KOR         : /^[ㄱ-ㅎㅏ-ㅣ가-힣]+$/,
    IS_NUM_KOR     : /^[0-9ㄱ-ㅎㅏ-ㅣ가-힣]+$/,
    IS_RRN         : /^(\d{2})(0[1-9]|1[0-2])(0[1-9]|1[0-9]|2[0-9]|3[0-1])(|\D)[1-4](\d{6})$/,
    IS_FGN         : /^(\d{2})(0[1-9]|1[0-2])(0[1-9]|1[0-9]|2[0-9]|3[0-1])(|\D)[5-8](\d{6})$/,
    IS_EMAIL       : /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/,
    IS_HOME_PHONE  : /^(0[2-8][0-5]?)(|\D)([1-9]{1}[0-9]{2,3})(|\D)([0-9]{4})$/,
    IS_CELL_PHONE1 : /^(01[1346-9])(|\D)([1-9]{1}[0-9]{2,3})(|\D)([0-9]{4})$/,
    IS_CELL_PHONE2 : /^(010)(|\D)([2-9]{1}[0-9]{3})(|\D)([0-9]{4})$/,

    UNMASK         : [ "X", "9", "*" ],

    MASK_CHR_STR   : "[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]",
    MASK_NUM_STR   : "[0-9]",
    MASK_ALL_STR   : ".",

    MASK_CHR       : /[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]/,
    MASK_NUM       : /[0-9]/,
    MASK_ALL       : /./,

    HAN_1ST          : ['ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ','ㅅ','ㅆ','ㅇ','ㅈ','ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ' ],
    HAN_2ND          : ['ㅏ','ㅐ','ㅑ','ㅒ','ㅓ','ㅔ','ㅕ','ㅖ','ㅗ','ㅘ','ㅙ','ㅚ','ㅛ','ㅜ','ㅝ','ㅞ','ㅟ','ㅠ','ㅡ','ㅢ','ㅣ'],
    HAN_3RD          : ['','ㄱ','ㄲ','ㄳ','ㄴ','ㄵ','ㄶ','ㄷ','ㄹ','ㄺ','ㄻ','ㄼ','ㄽ','ㄾ','ㄿ','ㅀ','ㅁ','ㅂ','ㅄ','ㅅ','ㅆ','ㅇ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ'],
    HAN_JOSA         : ['은','는','이','가','을','를'],
    HAN_JOSA_MERGE_1 : /(은\W는\W|는\W은\W)/,
    HAN_JOSA_MERGE_2 : /(이\W가\W|가\W이\W)/,
    HAN_JOSA_MERGE_3 : /(을\W를\W|를\W을\W)/

  },

  // logger
  LOGGER : {
    print  : false,
    dialog : true,
    window : {
      width    : 400,
      height   : 200,
      fontSize : 11
    }
  },

  // AJAX 설정
  AJAX : {
    debug    : false,     // 디버깅모드
    focus    : true,      // 송신 후 버튼 Focus 복원기능
    method   : "POST",    // GET/POST
    loading  : true,      // 기본 로딩바 표시여부
    dataType : "json",    // 데이터 전송타입 (json/jsonp)
    sync     : false,     // 동기전송 여부
    timeout  : 600 * 1000 // 조회대기시간 (sec * 1000)
  }

}

/**
*  Store Ui 표준 HashMap 객체
*
* <pre>
* var data = su.data();
*
* data.add( "merong", "1" );
* data.add( "merong", "2" );
* data.add( "nayasis", "3" );
*
* data.get( "merong" ); -> 2를 반환
* data.get( "nayasis" ); -> 3를 반환
* </pre>
*
* @return {su.data}
*/
su.data = function() {

  var z_self = arguments.callee;
  if(!(this instanceof z_self)) return new z_self();

  this.z_data = {};
}

su.data.prototype = {

  /**
   *  데이터를 세팅한다.
   * @param {String} key
   * @param {Object} value
   */
  add : function( key, value ) {
    if( su.chk.isNull(key) ) return;
    this.z_data[ key ] = value;
  },

  /**
   *  데이터를 삭제한다.
   * @param {String} key
   */
  del : function( key ) {
    if( su.chk.isNull(key) ) return;
    delete this.z_data[key];
  },

  /**
   *  데이터가 해당 키를 가지고 있는지 여부를 확인한다.
   * @param {String} key
   */
  hasKey : function( key ) {
    if( su.chk.isNull(key) ) return false;
    return ( su.chk.isNull(this.z_data[key]) ) ? false : true;
  },

  /**
   *  폼데이터를 더한다.<br>
   * 개체의 name 속성이 키, value 속성이 값으로 세팅된다.
   * @param {String} selector jquery 셀렉터
   */
  addForm : function( selector ) {

    var self = this;

    $(selector).each( function() {

      var th = $(this);

      if( th.is("[type=button]") ) return true;
      if( th.attr("name") == "" ) return true;

      if( th.is(":input") ) {
        if( th.is(":checkbox") && !th.is(":checked") ) return true;
        if( th.is(":radio")    && !th.is(":checked") ) return true;
        self.add( th.attr("name"), th.val() );

      } else {
        self.addForm( $(":input", th) );
      }

    });

  },

  /**
   *  키값에 해당하는 데이터를 추출한다.
   * @param {String} key 키
   */
  get : function( key ) {
    return this.z_data[ key ];
  },

  /**
   *  데이터를 초기화한다.
   */
  clear : function() {
    this.z_data = {};
  },

  /**
   *  데이터의 크기를 반환한다.
   * @return {Number} 데이터크기
   */
  size : function() {
    var z_max = 0;
    for( var c in this.z_data ) {
      z_max++;
    }
    return z_max;
  },

  /**
   *  입력한 정보의 헤더값을 반환한다.
   * @return {Array} 헤더정보
   */
  getHeader : function() {
    var header = [];
    for( var c in this.z_data ) {
      header.push( c );
    }
    return header;
  },

  /**
   *  JSON 형식의 raw 데이터를 반환한다.
   * @return {Object} JSON 데이터
   */
  getData : function() {
    return this.z_data;
  },

  /**
   *  데이터를 병합한다.
   * @param {su.data} mergeData 병합할 데이터
   */
  merge : function( mergeData ) {

    var src = this.z_data;
    var des = mergeData.getData();

    this.z_data = $.extend( src, des );

  },

  /**
   *  데이터가 동일한지 확인한다.
   * @param {su.data} data 비교할 데이터
   * @return {Boolean} 동일여부
   */
  equals : function( data ) {

    if( ! (data instanceof su.data) ) return false;

    var thisHdr = this.getHeader();
    var dataHdr = data.getHeader();

    if( thisHdr.length != dataHdr.length ) return false;

    for( var i = 0, iCnt = thisHdr.length; i < iCnt; i++ ) {

      var key = thisHdr[ i ];
      var src = this.get( key );
      var trg = data.get( key );

      if( trg  == undefined ) return false;
      if( src != trg ) return false;

    }

    return true;

  },

  /**
   * 직렬화된 데이터를 구한다.
   *
   * <pre>
   * var testData = su.data();
   * testData.add( "key1", "val1" );
   * testData.add( "key2", "val2" );
   *
   * testData.serialize(); -> "key1=val1&key2=val2" 반환
   * </pre>
   *
   * @return {String} 직렬화된 데이터
   */
  serialize : function() {

    var result = "";

    for( var key in this.z_data ) {
      result += ( key + "=" + (this.get( key ) || "") + "&" );
    }

    return result.substring( 0, result.length - 1 );

  }

}


/**
*  표준 List 객체
*
* <pre>
* var data = su.mdata();
*
* data.add( "merong", "1" );
* data.add( "merong", "2" );
* data.add( "nayasis", "3" );
*
* data.get( "merong", 0 ); -> 1를 반환
* data.get( "merong", 1 ); -> 2를 반환
* data.get( "nayasis", 0 ); -> 3를 반환
* </pre>
*
* @return {su.mdata}
*/
su.mdata = function() {

  var z_self = arguments.callee;
  if(!(this instanceof z_self)) return new z_self();

  this.z_data = {};

}

su.mdata.prototype = {

  /**
  *  데이터를 더한다.
  * @param {String} key 키
  * @param {Object} value 값
  */
  add : function( key, value ) {

    if( su.chk.isNull(key) ) return;
    if( su.chk.isNull(value) ) {
      value = "";
    } else {
      value = !isNaN(value) ? value : ( su.chk.isTypStr(value) ) ? value :$(value).val();
    }

    if( !this.z_data[key] ) {
      this.z_data[ key ] = [];
    }
    this.z_data[ key ].push( value );
  },

  /**
   *  데이터를 삭제한다.
   * @param {String} key 키
   */
  del : function( key ) {
    if( su.chk.isNull(key) ) return;
    delete this.z_data[key];
  },

  /**
  *  폼데이터를 더한다.<br>
  * 개체의 name 속성이 키, value 속성이 값으로 세팅된다.
  * @param {String} expression 셀렉터
  */
  addForm : function( expression ) {

    var self = this;

    $(expression).each( function() {

      var th = $(this);

      if( th.is("[type=button]") ) return true;

      if( th.is(":input") ) {

        var key = th.attr("name");
        if( su.chk.isEmpty(key) ) key = th.attr("id");

        if( su.chk.isEmpty(key) ) return true;
        if( th.is(":checkbox") && !th.is(":checked") ) return true;
        if( th.is(":radio")    && !th.is(":checked") ) return true;

        self.add( key, th );
        
      } else {
        self.addForm( $(":input", th) );
      }

    });

  },

  /**
   *  키와 인덱스 순서에 해당하는 값을 가져온다.
   * @param {String} key 키
   * @param {Number} index 인덱스
   * @return {String} 값
   */
  get : function( key, index ) {
    if( !this.z_data[key] ) return null;
    return this.z_data[ key ][ index ];
  },

  /**
   *  데이터를 초기화한다.
   */
  clear : function() {
    this.header = [];
    this.z_data = {};
  },

  /**
   *  데이터의 크기를 반환한다.
   * @return {Number} 데이터크기
   */
  size : function() {
    var z_max = 0;
    for( var c in this.z_data ) {
      z_max = Math.max( z_max, this.z_data[c].length );
    }
    return z_max;
  },

  /**
   *  헤더값을 반환한다.
   * @return {Array} 헤더값
   */
  getHeader : function() {
    var z_header = [];
    for( var c in this.z_data ) {
      z_header.push( c );
    }
    return z_header;
  },

  /**
   *  JSON 형식의 raw 데이터를 반환한다.
   * @return {Object} JSON 데이터
   */
  getData : function() {
    return this.z_data;
  },

  /**
   *  데이터의 크기를 균일하게 맞춘다.
   * @private
   */
  z_setMaxSize : function() {
    var size = this.size();
    for( var c in this.z_data ) {
      if( this.z_data[c].length < size ) {
        for( var inx = 1; inx < size; inx++ ) {
          if( this.z_data[c][inx] == undefined ) this.z_data[c][inx] = "";
        }
      }
    }
  },

  /**
   *  URI 인코딩된 데이터 값을 가져온다.
   * @return {mdata}
   */
  getUriEncodeData : function() {
    var result = this.z_data;
    for( var c in result ) {
      for( var inx = 0, inxCnt = result[c].length; inx < inxCnt; inx++ ) {
        result[c][inx] = encodeURIComponent( result[c][inx] );
      }
    }
    return result;
  }

}

/**
*  문자열 처리함수<br>
* <pre>
* alert( su.str("M123erong").getNum().val() ); -> "123"  출력
* </pre>
* 
* @param  {String} string
* @return {su.str}
* 
*/
su.str = function( string ) {

  var z_self = arguments.callee;

  if( !(this instanceof z_self) ) return new z_self( string );
  
  this.init( string );

}

su.str.prototype = {

  /**
   * 값을 세팅한다.
   * 
   * @private
   * @param {String} value
   */
  init : function( value ) {

    if( su.chk.isNull(value) ) {
      this.z_val = "";
    } else if( su.chk.isTypStr(value) ) {
      this.z_val = value;
    } else if( value instanceof su.str ) {
      this.z_val = value.val();
    } else if( su.chk.isTypObj(value) ) {
      this.z_val = "" + $(value).val();
    } else {
      this.z_val = "" + value;
    }
    
  },
  
  /**
  * 처리된 문자열을 가져온다.<br>
  * 처리에 필요한 문자열을 세팅할 수도 있다.
  * 
  * @param {String} value 값을 넣으면 세팅, 값을 넣지 않으면 데이터 조
  * @return {su.str}
  */
  val : function( value ) {

    if( arguments.length == 0 ) return this.z_val;
    
    init( value );

    return this;

  },

  /**
  * 문자열을 더한다.<br>
  * 
  * @param {String} value
  * @return {su.str}
  */
  concat : function( value ) {

    if( value != null && value.length != 0 ) {
      this.z_val += value;
    }

    return this;

  },
  
  /**
  *  문자열을 치환한다.
  * <pre>
  * alert( su.str( "my body is male" ).replace("male", "female").val() );
  * --> "my body is female"
  * </pre>
  * @param {String,RegExp} pattern  찾을 문자열 또는 정규식 패턴
  * @param {String} replaceStr  치환할 문자열
  * @return {su.str}
  */
  replace : function( pattern, replaceStr ) {

    if( pattern != null && (su.chk.isTypStr(replaceStr) || su.chk.isTypNum(replaceStr)) ) {
      this.z_val = this.z_val.replace( pattern, replaceStr );
    }

    return this;

  },

  /**
  *  문자열을 모두 치환한다.
  * <pre>
  * alert( su.str( "m A m" ).replace("m", "a").val() ); --> "a A m"
  * alert( su.str( "m A m" ).replaceAll("m", "a").val() ); --> "a A a"
  * </pre>
  * @param {String} pattern  찾을 문자열
  * @param {String} replaceStr  치환할 문자열
  * @return {su.str}
  */
  replaceAll : function( pattern, replaceStr ) {
    var ptn = ( su.chk.isTypStr(pattern) || su.chk.isTypNum(replaceStr) ) ? eval( "/" + pattern + "/g" ) : pattern;
    return this.replace( ptn, replaceStr );
  },

  
  /**
  *  문자열을 없앤다.
  * @param {String,RegExp} pattern  찾을 문자열 또는 정규식 패턴
  * @return {su.str}
  */
  remove : function( pattern ) {
    return this.replace( pattern, "" );
  },

  /**
  *  문자열을 모두 없앤다.
  * @param {String,RegExp} pattern  찾을 문자열 또는 정규식 패턴
  * @return {su.str}
  */
  removeAll : function( pattern ) {
    return this.replaceAll( pattern, "" );
  },

  /**
  *  앞뒤 공백문자를 제거한다.
  * @return {su.str}
  */
  trim : function() {
    return this.remove( su.c.REG_PTN.TRIM );
  },

  /**
  *  앞 공백문자를 제거한다.
  * @return {su.str}
  */
  ltrim : function() {
    return this.remove( su.c.REG_PTN.LTRIM );
  },

  /**
  *  뒤 공백문자를 제거한다.
  * @return {su.str}
  */
  rtrim : function() {
    return this.remove( su.c.REG_PTN.RTRIM );
  },

  /**
  *  모든 공백문자를 제거한다.
  * @return {su.str}
  */
  allTrim : function() {
    return this.remove( su.c.REG_PTN.INNER_SPACE );
  },

  /**
  *  중복 빈칸을 1개의 빈칸으로 만든다.
  * @return {su.str}
  */
  innerTrim : function() {
    return this.replace( su.c.REG_PTN.INNER_TRIM, " " );
  },

  /**
  *  LPAD 처리를 수행한다.
  * <pre>
  * alert( su.str("A").lpad(3,'0').val() ); -> 00A
  * </pre>
  * @param {Number} length 전체길이
  * @param {String} fillChar 패딩문자
  * @return {su.str}
  */
  lpad : function( length, fillChar ) {

    if( !su.chk.isEmpty(this.z_val) ) {
      var srcStr = this.z_val.substr( 0, length );
      var cnt = 0;
      for( var i = srcStr.length; i < length; i++ ) {
        srcStr = fillChar.charAt(cnt) + srcStr;
        cnt++;
        cnt = ( cnt == fillChar.length ) ? 0 : cnt;
      }
      this.z_val = srcStr;
    }

    return this;

  },

  /**
  *  RPAD 처리를 수행한다.
  * <pre>
  * alert( su.str("A").rpad(3,'0').val() ); -> A00
  * </pre>
  * @param {Number} length 전체길이
  * @param {String} fillChar 패딩문자
  * @return {su.str}
  */
  rpad : function( length, fillChar ) {

      if( !su.chk.isEmpty(this.z_val) ) {
        var srcStr = this.z_val.substr( 0, length );
        var cnt = 0;
        for( var i = srcStr.length; i < length; i++ ) {
          srcStr = srcStr + fillChar.charAt(cnt);
          cnt++;
          cnt = ( cnt == fillChar.length ) ? 0 : cnt;
        }
        this.z_val = srcStr;
      }

      return this;

  },

  /**
  *  byte 수를 확인한다.
  * @return {Number} 문자열의바이트
  */
  bytes : function() {
    var z_add = ( su.c.CHECK_CHRSET == "UTF-8" ) ? 2 : 1;
    var z_len = 0;
    for( var i = 0, cnt = this.z_val.length; i < cnt; i++, z_len++ ) {
      if( this.z_val.charCodeAt(i) < 0 || this.z_val.charCodeAt(i) > 127 ) z_len += z_add;
    }
    return z_len;
  },

  /**
  *  숫자만 남긴다.
  * @return {su.str}
  */
  setNum : function() {
    return this.remove( su.c.REG_PTN.NOT_NUM );
  },

  /**
  *  소수점을 포함한 숫자만 남긴다.
  * @return {su.str}
  */
  setNumeric : function() {
    return this.remove( su.c.REG_PTN.NOT_NUMERIC );
  },

  /**
  *  부호와 소수점을 포함한 숫자만 남긴다.
  * @return {su.str}
  */
  setParseNum : function() {
    return this.remove( su.c.REG_PTN.NOT_NUM_PARSE );
  },

  /**
  *  숫자만 제외시킨다.
  * @return {su.str}
  */
  setNotNum : function() {
    return this.remove( su.c.REG_PTN.NUM );
  },

  /**
  *  정수 형태로 추출한다.
  * @return {Number} 정수
  */
  parseInt : function() {
    this.setParseNum();
    if( this.z_val == "" ) return 0;
    return parseInt( this.z_val );
  },

  /**
   *  실수 형태로 추출한다.
   * @return {Number} 실수
   */
  parseFloat : function() {
    this.setParseNum();
    if( this.z_val == "" ) return 0.0;
    return parseFloat( this.z_val );
  },

  /**
  *  영문자만 남긴다.
  * @return {su.str}
  */
  setEng : function() {
    return this.remove( su.c.REG_PTN.NOT_ENG );
  },

  /**
  *  영문자만 제외시킨다.
  * @return {su.str}
  */
  setNotEng : function() {
    return this.remove( su.c.REG_PTN.ENG );
  },

  /**
  *  숫자에 3자리마다 콤마를 찍는다.
  * @return {su.str}
  */
  setMoney : function() {

    var _val = "" + this.parseFloat();
    var _pos = _val.lastIndexOf(".");

    if( _pos >= 0 ) {
      this.z_val = _val.substring( 0, _pos - 1).replace( su.c.REG_PTN.MONEY, "$1," ) + _val.substring( _pos );
    } else {
      this.z_val = _val.replace( su.c.REG_PTN.MONEY, "$1," );
    }

    return this;

  },

  /**
  *  값이 없다면 지정 문자로 치환한다.
  * @param {String} replaceStr 값이 없을 경우 치환할 문자열
  * @return {su.str}
  */
  nvl : function( replaceStr ) {
    if( this.z_val == "" ) this.val( replaceStr );
    return this;
  },

  /**
  *  문자열이 특정 패턴을 가지고 있는지 검사한다.
  * @param {String} pattern  패턴
  * @return {Boolean}
  */
  hasValue : function( pattern ) {
    if( pattern == null ) return false;
    return ( this.z_val.indexOf( pattern ) >= 0 ) ? true : false;
  },

  /**
  *  문자열이 비어있는지 검사한다.
  * @return {Boolean}
  */
  isEmpty : function( pattern ) {
    return this.z_val == "";
  },

  /**
  *  마지막 한글문자의 초성/중성/종성을 나누어 가져온다.
  * <pre>
  * var c = su.str( "정화수" ).getLastKrChr(); -> ['ㅅ','ㅜ','']
  * var c = su.str( "손상욱" ).getLastKrChr(); -> ['ㅇ','ㅜ','ㄱ']
  * var c = su.str( "Name" ).getLastKrChr(); -> [null,null,null]
  * </pre>
  * @return {Array} 초성/중성/종성이 나누어진 배열 (한글이 아닐 경우 null이 반환됨)
  */
  getLastKr : function() {

    var result = [null,null,null];
    var word   = this.z_val;
    var schar  = word.charCodeAt( word.length - 1 );

    if( schar < 0xAC00 || schar > 0xD79F ) return result;

    schar = schar - 0xAC00;

    var jong = schar % 28;
    var jung = ( (schar - jong) / 28 ) % 21;
    var cho  = parseInt( ((schar-jong) / 28) / 21 );

    result[0] = su.c.REG_PTN.HAN_1ST[cho];
    result[1] = su.c.REG_PTN.HAN_2ND[jung];
    result[2] = su.c.REG_PTN.HAN_3RD[jong];

    return result;

  },

  /**
   *  문자열 길이를 구한다.
   * @return {Number}
   */
  length : function() {
    return this.z_val.length;
  },

  /**
   *  시작위치부터 지정 길이만큼 문자열을 자른다.
   * @param {Number} start  시작위치
   * @param {Number} length 길이
   * @return {su.str}
   */
  substr : function( start,length ) {
    this.z_val = this.z_val.substr( start, length );
    return this;
  },

  /**
   *  지정한 위치대로 문자열을 자른다.
   * @param {Number} startIndex  시작위치
   * @param {Number} endIndex    종료위치
   * @return {su.str}
   */
  substring : function( startIndex, endIndex ) {
    this.z_val = this.z_val.substring( startIndex, endIndex );
    return this;
  },

  /**
   * 문자열이 시작하는 위치를 반환한다.
   * <pre>
   * su.str( "Play Station" ).indexOf( "y" ); ->  3 을 반환
   * </pre>
   * @param {String} searchValue 검색할 문자열
   * @param {Number} fromIndex   검색시작위치
   * @return {Number} 문자열 검색위치
   */
  indexOf : function( searchValue, fromIndex ) {
    return this.z_val.indexOf( searchValue, fromIndex );
  },

  /**
   * 문자열이 시작하는 위치를 뒤부터 검색하여 반환한다.
   * <pre>
   * su.str( "xbox" ).lastIndexOf( "x" ); -> 3 을 반환
   * </pre>
   * @param {String} searchValue  검색할 문자열
   * @param {Number} fromIndex    검색시작위치
   * @return {Number} 문자열 검색위치
   */
  lastIndexOf : function( searchValue, fromIndex ) {
    return this.z_val.lastIndexOf( searchValue, fromIndex );
  },


  /**
   *  대문자로 변환한다.
   * <pre>
   * su.str( "xbox" ).toUpperCase().val(); -> "XBOX" 반환
   * </pre>
   * @return {su.str}
   */
  toUpperCase : function() {
    this.z_val = this.z_val.toUpperCase();
    return this;
  },

  /**
   *  소문자로 변환한다.
   * <pre>
   * su.str( "XboX" ).toLowerCase().val(); -> "xbox" 반환
   * </pre>
   * @return {su.str}
   */
  toLowerCase : function() {
    this.z_val = this.z_val.toLowerCase();
    return this;
  },

  /**
   *  FORMAT 형식의 날짜문자열을 [YYYY,MM,DD,HH,MI,SS] 순서의 배열로 읽어온다.
   * <pre>
   * alert( su.str("03-23-2011 12:32").getDateArray("MM-DD-YYYY MI SS") ); -> [2011,03,23,,12,32]
   * </pre>
   * @param {String} format   포맷 (YYYY,MM,DD,HH,MI,SS 조합)
   * @return {Array}
   */
  getDateArray : function( format ) {

    var z_format  = su.str(format||su.conf.com.dateFormat).setEng().toUpperCase().val();
    var z_dateStr = this.setNum().val();

    var z_div = [ 'YYYY', 'MM', 'DD', 'HH', 'MI', 'SS' ];
    var z_pos = [], z_res = [];

    // get position according to format
    $(z_div).each(function(i){
      z_pos.push( z_format.indexOf(z_div[i]) );
    });

    // extract data
    $(z_div).each(function(i){
      if( z_pos[i] < 0 ) {
        z_res[i] = null;
        return true;
      }
      z_res[i] = z_dateStr.substr( z_pos[i], z_div[i].length );
    });

    return z_res;

  },

  /**
   * 문자열을 배열로 만든다.
   *
   * @param {String} seperator  배열을 나눌 구분기호
   * @param {Number} limit      최대 배열 개수
   * @return {Array} 구분기호로 분리된 배열
   */
  split : function( seperator, limit ) {
    return this.z_val.split( seperator || "", limit );
  },

  /**
   * 마스크를 적용한다.
   *
   * @param {String} format 패턴 ( X:문자, 9:숫자, *:문자 또는 숫자 )
   * @return {su.str}
   */
  setMask : function( format ) {

    var ptn = su.str(format).val().toUpperCase();

    var result = "";

    var flag, j = 0, chStr, chPtn;

    for( var i = 0, iCnt = ptn.length; i < iCnt; i++ ) {

      flag = true;

      chStr = this.z_val.charAt(j);
      chPtn = ptn.charAt(i);

      if( chStr == "" ) break;

      switch( chPtn ) {

        case "X":
          if( ! su.chk.isReg(chStr, su.c.REG_PTN.MASK_CHR) ) flag = false;
          break;

        case "9" :
          if( ! su.chk.isReg(chStr, su.c.REG_PTN.MASK_NUM) ) {
            flag = false;
          }
          break;

        case "*" :
          if( ! su.chk.isReg(chStr, su.c.REG_PTN.MASK_ALL) ) flag = false;
          break;

        default :

          if( chStr == chPtn ) j++;
          result += chPtn;
          continue;

      }

      if( flag == true ) {
        result += chStr;
      } else {
        i--;
      }

      j++;

    }

    this.z_val = result;

    return this;

  },

  /**
   * 마스크를 해제한다.
   *
   * @param {String} format 패턴 ( X:문자, 9:숫자, *:문자 또는 숫자 )
   * @return {su.str}
   */
  setUnmask : function( format ) {

    var ptn = su.str(format).remove(/[x|X|9|\*]/g).split();

    ptn = $.unique( ptn );

    for( var i = 0, iCnt = ptn.length; i < iCnt; i++ ) {
      this.removeAll( ptn[i] );
    }

    return this;

  },

  /**
  * 문자열의 XSS 처리를 해제한다.
  * @return {su.str}
  */
  unclearXss : function() {

     this.z_val = this.z_val
      .replace( /&lt;/g,  "<" )
      .replace( /&gt;/g,  ">" )
      .replace( /&#60;/g, "<" )
      .replace( /&#62;/g, ">" )
      .replace( /&#34;/g, '"' )
      .replace( /&#39;/g, "'" )
      .replace( /&#92;/g, "W" )
      .replace( /&#40;/g, "(" )
      .replace( /&#41;/g, ")" );

     return this;

  },

  /**
  * 문자열에 XSS 처리를 수행한다.
  * @return {su.str}
  */
  clearXss : function() {

     this.z_val = this.z_val
      .replace( /</g, "&#60;" )
      .replace( />/g, "&#62;" )
      .replace( /"/g, "&#34;" )
      .replace( /'/g, "&#39;" )
      .replace( /\\/g,"&#92;" )
      .replace( /\(/g,"&#40;" )
      .replace( /\)/g,"&#41;" );

     return this;

  }
  
}

/**
* 정합성 체크 유틸리티
* @classDescription 정합성 체크 유틸리티
* @author 정화수 2011.03.24
*/
su.chk = {

  /**
  *  입력값이 null인지 여부를 확인한다.<br>
  * @param  {Object}  obj  검사할객체
  * @return {Boolean} <b>null, undefined</b> 인 경우 true를 반환
  * @author 정화수 (2007.06.26)
  */
  isNull : function( obj ) {
    return ( obj == null || obj == undefined ) ? true : false;
  },

  /**
   *  입력항목의 값이 비어있는지 여부를 확인한다.
   * @param {Object, String} obj 검사할객체
   * @return {Boolean} <b>null, undefined, ""</b> 인 경우 true를 반환
   */
  isEmpty : function( obj ) {
    return ( obj == null || obj === "" || obj == undefined ) ? true : false;
  },

  /**
   *  정규식을 테스트한다.
   * <pre>
   * alert( su.chk.isReg( "aaaab a", /.*b/ ) );
   * </pre>
   * @param {String} string   패턴검사를 수행할 문자열
   * @param {Object} pattern  정규식패턴 (또는 문자열)
   * @return {Boolean} 테스트 성공여부
   */
  isReg : function( string, pattern ) {
    if( su.chk.isEmpty(string) || su.chk.isNull(pattern) ) return false;
    return pattern.test( su.str(string).val() );
  },

  /**
   *  HTML 태그가 포함되어있는지 여부를 확인한다.
   * @param {String} string 패턴검사를 수행할 문자열
   * @return {Boolean} 태그 포함여부
   */
  hasHtml : function( string ) {
    return su.chk.isReg( string, su.c.REG_PTN.HAS_HTML );
  },

  /**
   *  String 타입 여부를 확인한다.
   * @param {Object} string 검사할 개체
   * @return {Boolean} 타입이 문자일 경우 true 반환
   */
  isTypStr : function( string ) {
    return typeof string == "string";
  },

  /**
   *  Object 타입 여부를 확인한다.
   * @param {Object} obj 검사할 개체
   * @return {Boolean}
   */
  isTypObj : function( obj ) {
    return typeof obj == "object";
  },

  /**
   *  Date 타입 여부를 확인한다.
   * @param {Object} date 검사할 개체
   * @return {Boolean}
   */
  isTypDate : function( date ) {
    return date instanceof Date;
  },

  /**
   *  Number 타입 여부를 확인한다.
   * @param {Object} number 검사할 개체
   * @return {Boolean}
   */
  isTypNum : function( number ) {
    return typeof number == "number";
  },

  /**
   *  Function 타입 여부를 확인한다.
   * @param {Object} fn 검사할 개체
   * @return {Boolean} 타입이 함수일 경우 true 반환
   */
  isTypFn : function( fn ) {
    return typeof fn == "function";
  },

  /**
   *  Array 타입 여부를 확인한다.
   * @param {Object} array  검사할 개체
   * @return {Boolean} 타입이 배열일 경우 true 반환
   */
  isTypArr : function( array ) {
    return array instanceof Array;
  },

  /**
   *  숫자로만 구성되어있는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isNum : function( string ) {
    return su.chk.isReg( string, su.c.REG_PTN.IS_NUM );
  },

  /**
   *  수치자료인지 여부를 확인한다. (소수점을 포함한 경우도 체크 가능)
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isNumeric : function( string ) {

    var val = su.str( string ).setNumeric();

    if( val.length() == 0 ) return false;

    return ! isNaN( val.val() );
  },

  /**
   *  숫자가 없는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isNotNum : function( string ) {
    if( su.chk.isEmpty(string)) return true;
    return su.chk.isReg( string, su.c.REG_PTN.IS_NOT_NUM );
  },

  /**
   *  한글이 없는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isNotKor : function( string ) {
    if( su.chk.isEmpty(string)) return true;
    return su.chk.isReg( string, su.c.REG_PTN.IS_NOT_KOR );
  },

  /**
   *  영문자로만 구성되어있는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isEng : function( string ) {
    return su.chk.isReg( string, su.c.REG_PTN.IS_ENG );
  },

  /**
   *  영문자와 숫자로만 구성되어있는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isNumEng : function( string ) {
    return su.chk.isReg( string, su.c.REG_PTN.IS_NUM_ENG );
  },

  /**
   *  한글로만 구성되어있는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isKor : function( string ) {
    return su.chk.isReg( string, su.c.REG_PTN.IS_KOR );
  },

  /**
   *  한글과 숫자로만 구성되어있는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isNumKor : function( string ) {
    return su.chk.isReg( string, su.c.REG_PTN.IS_NUM_KOR );
  },

  /**
   *  주민등록번호 여부를 확인한다.
   * @param {String} string 검사할 문자열
   * @return {Boolean} 검사결과
   */
  isPsn : function( string ) {

    // 형식 체크
    if( ! su.chk.isReg(string, su.c.REG_PTN.IS_RRN) ) return false;
    var _rrn = su.str(string).setNum().val();

    // 날짜정합성 체크
    var _birth = su.chk.isReg( _rrn.substr(6,1), /[1|2]/ ) ? "19" : "20";
    _birth += _rrn.substr(0,6);

    if( su.date(_birth).val() == null ) return false;

    // 체크섬
    var _sum = 0;
    var _chk = [ 2,3,4,5,6,7,8,9,2,3,4,5 ];

    for(var inx = 0; inx < 12; inx++ )
      _sum += ( Number(_rrn.charAt(inx)) * _chk[inx] );

    return !((11 - (_sum % 11)) % 10 != Number(_rrn.charAt(12)));

  },

  /**
   *  이메일 여부를 확인한다.
   * @param {Object|String} obj 검사할 개체
   * @return {Boolean} 검사결과
   */
  isEmail : function( obj ) {
    return su.chk.isReg( obj, su.c.REG_PTN.IS_EMAIL );
  },

  /**
   *  검사항목의 값이 최대 바이트수 이하인지 확인한다.
   * @param {String} string 검사할 개체
   * @param {Number} chkByte 최대 바이트수
   * @return {Boolean} 검사결과
   */
  isMaxByte : function( string, chkByte ) {
    return su.str(string).bytes() <= chkByte;
  },

  /**
   *  검사항목의 값이 최소 바이트수 이상인지 확인한다.
   * @param {String} string 검사할 개체
   * @param {Number} chkByte 최소 바이트수
   * @return {Boolean} 검사결과
   */
  isMinByte : function( string, chkByte ) {
    return su.str(string).bytes() >= chkByte;
  },

  /**
   *  검사항목의 값이 최대 길이 이하인지 확인한다.
   * @param {String} string 검사할 개체
   * @param {Number} chkLen 최대 길이
   * @return {Boolean} 최대길이 이하일 경우 true를 반환
   */
  isMaxLen : function( string, chkLen ) {
    return su.str(string).length() <= chkLen;
  },

  /**
   *  검사항목의 값이 최소 길이 이상인지 확인한다.
   * @param {String} string 검사할 개체
   * @param {Number} chkLen 최소 길이
   * @return {Boolean} 최소길이 이하일 경우 true를 반환
   */
  isMinLen : function( string, chkLen ) {
    return su.str(string).length() >= chkLen;
  },

  /**
   *  올바른 날짜인지 여부를 확인한다.
   * @param {String} obj 검사할 개체
   * @param {String} format 날짜포맷 (YYYY:년도, MM:월, DD:일, HH:시, HI:분, SS:초)
   * @return {Boolean} 검사결과
   */
  isDate : function( string, format ) {
    return su.date( string, format ).val() != null;
  },

  /**
   *  일반 전화번호 형식 여부를 확인한다.
   * @param {String} string 검사할 문자열
   * @return {Boolean} 검사결과
   */
  isHomePhone : function( string ) {
    return su.chk.isReg( string, su.c.REG_PTN.IS_HOME_PHONE );
  },

  /**
   *  핸드폰 전화번호 형식 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isCellPhone : function( string ) {

    var p = su.str(string).substr(0,3).val();

    return su.chk.isReg( obj, ( p == "010" ) ? su.c.REG_PTN.IS_CELL_PHONE2 : su.c.REG_PTN.IS_CELL_PHONE1 );

  },

  /**
   *  정의한 MASK형식에 맞는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @param {String} format 패턴 ( X:문자, 9:숫자, *:문자 또는 숫자 )
   * @return {Boolean} 검사결과
   */
  isMasked : function( string, format ) {

    var ptn = su.str(format).val();

    ptn = ptn.replace( /[x|X]/g, su.c.REG_PTN.MASK_CHR_STR );
    ptn = ptn.replace( /[9]/g,   su.c.REG_PTN.MASK_NUM_STR );
    ptn = ptn.replace( /\*/g,    su.c.REG_PTN.MASK_ALL_STR );

    ptn = eval( "/" + ptn + "/g" );

    return su.chk.isReg( string, ptn );

  },

  /**
   *  국내거소신고번호(외국인등록번호,재외국민등록번호) 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isFsn : function( string ) {

    // 형식 체크
    if( ! su.chk.isReg(obj, su.c.REG_PTN.IS_FGN) ) return false;
    var _rrn = su.str(string).setNum().val();

    if(((_rrn.substr(7,1) * 10) + _rrn.substr(8,1)) % 2 != 0)
      return false;

    // 날짜정합성 체크
    var _birth = su.chk.isReg( _rrn.substr(6,1), /[5|6]/ ) ? "19" : "20";
    _birth += _rrn.substr(0,6);
    _birth = su.obj.getDate( _birth );
    if( _birth == null ) return false;

    // 등록기관체크(7.외국국적동포, 8.재외국민, 9.외국인)
    if( !su.chk.isReg( _rrn.substr(11,1), /[7|8|9]/ ) )
      return false;

    // 체크섬
    var _sum = 0;
    var _chk = [ 2,3,4,5,6,7,8,9,2,3,4,5 ];

    for(var inx = 0; inx < 12; inx++ )
      _sum += ( Number(_rrn.charAt(inx)) * _chk[inx] );

    return !(((11 - (_sum % 11)) + 2) % 10 != Number(_rrn.charAt(12)));

  },

  /**
   *  사업자등록번호 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isCsn : function( string ) {

    var _bun = su.str(string).setNum().val();
    if( !su.chk.isReg(_bun, su.c.REG_PTN.IS_NUM) || !su.chk.isReg(_bun, /^\d{10}$/)) return false;

    var ckValue = new Array(10);
    ckValue[0] = ( parseFloat(_bun.substring(0 ,1)) * 1 ) % 10;
    ckValue[1] = ( parseFloat(_bun.substring(1 ,2)) * 3 ) % 10;
    ckValue[2] = ( parseFloat(_bun.substring(2 ,3)) * 7 ) % 10;
    ckValue[3] = ( parseFloat(_bun.substring(3 ,4)) * 1 ) % 10;
    ckValue[4] = ( parseFloat(_bun.substring(4 ,5)) * 3 ) % 10;
    ckValue[5] = ( parseFloat(_bun.substring(5 ,6)) * 7 ) % 10;
    ckValue[6] = ( parseFloat(_bun.substring(6 ,7)) * 1 ) % 10;
    ckValue[7] = ( parseFloat(_bun.substring(7 ,8)) * 3 ) % 10;
    var ckTemp = String((parseFloat(_bun.substring(8 ,9)) * 5)) + "0";
    ckValue[8] = parseFloat( ckTemp.substring(0,1) ) + parseFloat( ckTemp.substring(1,2) );
    ckValue[9] = parseFloat(_bun.substring(9,10));
    var ckLastid = (10 - (( ckValue[0]+ckValue[1]+ckValue[2]+ckValue[3]+ckValue[4]+ckValue[5]+ckValue[6]+ckValue[7]+ckValue[8] ) % 10 )) % 10;

    return !(ckValue[9] != ckLastid);

  },

  /**
   *  법인등록번호 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isLsn : function( string ) {

    // 형식 체크
    var _bin = su.str(string).setNum().val();
    if( !su.chk.isReg(_bin, su.c.REG_PTN.IS_NUM) || !su.chk.isReg(_bin, /^\d{13}$/))
      return false;

    var _chk = [ 1,2,1,2,1,2,1,2,1,2,1,2 ];
    var _sum = 0;
    for(var i = 0; i < _chk.length; i++){
      _sum += _chk[i] * _bin.substring(i ,i+1);
    }

    return ((10 - _sum % 10) % 10 == _bin.substring(12 ,13));

  },

  /**
   *  영문자와 숫자 두가지 혼합으로 구성되어있는지 여부를 확인한다.
   * @param {String} string 검사할 개체
   * @return {Boolean} 검사결과
   */
  isNumAndEng : function( string ) {

    if( ! su.chk.isReg( string, su.c.REG_PTN.IS_NUM_ENG ) ) return false;
    return (!su.chk.isReg( string, su.c.REG_PTN.IS_NUM ) && !su.chk.isReg( string, su.c.REG_PTN.IS_ENG));

  }

}


/**
* 로딩바를 보여준다.<br>
* 
* (버튼 차단 역할도 수행)
*/
su.loadingBar = {

  z_showCnt : 0,
  z_enable  : true,
  
  /**
   *  로딩바를 보여준다.
   */
  show : function() {
    
    if( ! su.loadingBar.z_enable ) return;
    
    su.loadingBar.z_showCnt ++;
    
    if( su.loadingBar.z_showCnt > 1 ) return;
    
    $.blockUI({ message: '<h4>잠시만 기다려 주세요.</h4>' });

  },

  /**
  *  로딩바를 감춘다.
  */
  hide : function() {

    if( ! su.loadingBar.z_enable ) return;
    
    su.loadingBar.z_showCnt --;
    
    if( su.loadingBar.z_showCnt > 0 ) return;
    
    $.unblockUI();

  },

  /**
   * 로딩바 기능을 활성화시킨다.
   */
  enable : function() {
    su.loadingBar.z_enable = true;
  },

  /**
   * 로딩바 기능을 비활성화시킨다.
   */
  disable : function() {
    su.loadingBar.z_enable = false;
  }
  
}

/**
* 서버호출모듈
* @classDescription komafUI 서버호출모듈
* @author 정화수, 2011.03.22
*/
su.req = {

    /**
     * @return {su.data}
     */
    z_req  : su.mdata(),
    data   : {}, // 동기호출시 통신결과가 저장되는 장소

    z_ajax : {

      /**
       *  서버와 통신이 성공했을 경우 수행한다.
       * @param {Object} res 결과값이 담겨있는 JSON 오브젝트 (data,meta)
       * @param {Object} options 옵션
       * @private
       */
      onComplete : function( res, options ) {
      
        if( options.loading == true ) su.loadingBar.hide();
      
        // 동기방식일 경우 결과값을 전역변수에 세팅
        if( options.sync == true ) {
          su.req.data = res;
        }

        // 후처리함수가 세팅되었다면 이를 실행
        if( su.chk.isTypFn(options.func) ) {
          options.func( res );
        }

      },
      
      /**
       *  서버와 통신 자체를 실패했을 경우 실행된다.
       * @param {Object} xhr
       * @private
       */
      onError : function( xhr, options ) {

        if( options.loading == true ) su.loadingBar.hide();
      
        // 동기방식일 경우 결과값을 전역변수에 세팅
        if( options.sync == true ) {
          su.req.data = {}
        }
      
        var errMsg = "";
        var header = xhr.getAllResponseHeaders();
        if( header == null ) header = "\n";
        errMsg += su.msg.get("su.api.xhr.errTitle");
        errMsg += "----------------------------------------------\n";
        switch( xhr.status ) {
          case 200 : errMsg += su.msg.get("su.api.xhr.err200"); break;
          case 404 : errMsg += su.msg.get("su.api.xhr.err404"); break;
          case 0   : errMsg += su.msg.get("su.api.xhr.err000"); break;
        }
        errMsg += "----------------------------------------------\n";
        errMsg += ">> Status : " + xhr.status + "\n";
        errMsg += ">> ReadyState : " + xhr.readyState + "\n";
        errMsg += "----------------------------------------------\n";
      
        su.msg.alert( errMsg );
      
      },
      
      /**
      *  서버에 요청을 날린다.
      * <pre>
      * su.req.z_ajax.send( "/test/test/TestJqAjaxCmd.ajax", {method:'GET', asyntrue, loading:false} );
      * </pre>
      * @private
      * @param    {String}  url     호출URL   
      * @param    {Object}  options 옵션
      * <pre>
      * domain  : {String}   URL의 메인도메인 (기본값 : rootContext, 외부링크로 전송하고 싶을 경우 해당 항목을 변경한다.)
      * func    : {Function} 비동기 방식일 때 호출할 CallBack 함수 (기본값:null)
      * sync    : {Boolean}  동기방식 호출여부 (기본값:false)
      * method  : {String}   전송형식 (POST/GET), (기본값:POST)
      * timeout : {Number}   호출 대기시간 (기본값 : su.c.AJAX.timeout 설정값 )
      * loading : {Boolean}  로딩바 표시여부 (기본값:true)
      * alert   : {Boolean}  서버에서 보내주는 메세지 표시여부 (기본값:true)
      * </pre>
      * @author Hwasu, 2011.03.22
      */
      send : function( url, options ) {
    	  // debugger;
        var op = $.extend({
          domain   : su.c.ROOT_PATH,
          func     : null,
          method   : su.c.AJAX.method,
          dataType : su.c.AJAX.dataType,
          sync     : su.c.AJAX.sync,
          loading  : su.c.AJAX.loading,
          alert    : true,
          timeout  : su.c.AJAX.timeout
        }, options || {} );
      
        if( op.loading == true ) su.loadingBar.show();
      
        var reqData = su.req.z_req.z_data;
        
        jQuery.ajaxSettings.traditional = true;
        
        // call ajax
        $.ajax({
          global   : false,
          url      : op.domain + url,
          type     : op.method,
          data     : reqData,
          dataType : op.dataType,
          timeout  : op.timeout,
          async    : !op.sync,
          success  : function(data) { su.req.z_ajax.onComplete(data,op) },
          error    : function(xhr) { su.req.z_ajax.onError(xhr,op) }
        });
      
        // clear request param
        su.req.z_req.clear();
      
        // return result when SJAX called
        if( op.sync == true ) return su.req.data;
      
      }
    
    },

    /**
     *  동기호출시 호출에러 여부가 저장되는 장소
     * @return {Boolean}
     */
    err : false,
    
    /**
     *  파라미터를 추가한다.
     * @param {String} key 키
     * @param {String} value 값
     */
    addParam : function( key, value ) {
      su.req.z_req.add( key, value );
    },

    /**
    * FORM 파라미터를 추가한다.
    * @param {String} expression  셀럭터
    */
    addForm : function( expression ) {
      su.req.z_req.addForm( expression );
    },

    /**
     *  세팅한 파라미터를 초기화한다.
     */
    clearParam : function() {
      su.req.z_req.clear();
    },

    /**
     * JSON 파라미터를 세팅한다.
     * 
     * @param {Object}  jsonParam JSON 형식의 파라미터
     * @param {Boolean} merge     기존 파라미터와 병합 여
     */
    setParam : function( jsonParam, merge ) {
      
      if( merge == true ) {
        su.req.z_req.z_data = $.extend( su.req.z_req.z_data, jsonParam || {} );
      } else {
        su.req.z_req.z_data = jsonParam;
      }
      
    },
    
    /**
     * 세팅된 파라미터를 꺼내온다.
     * 
     * @returns {su.data}
     */
    getParam : function() {
      return su.req.z_req;
    },
    
    /**
    *  서버에 요청을 날린다.
    * <pre>
    * su.req.send( "/test/test/TestJqAjaxCmd.ajax", fnCallBack, {method:'GET', sync:true, loading:false} );
    * su.req.send( "/test/test/TestJqAjaxCmd.ajax", function( data, err ) {
    *   if( err ) return;
    *   if( data == null || data.someResult == null ) return;
    *   // TO-DO something
    * });
    * </pre>
    * @param  {String}   url     호출URL
    * @param  {Function} fn      후처리함수 (Ajax 호출 후 실행됨)
    * <pre>
    * 1. function fnCallBack( 결과데이터, 에러여부 ) {} 형식을 취해야 함
    * 2. 서버의 CMD 클래스에서 Output.addParam( "keyName1", 데이터 ); 로 세팅한 값은
    *    결과데이터.keyName1 에 담겨 들어온다.
    * 3. 에러여부 변수의 true/false 비교로 에러 여부를 확인할 수 있다.
    * </pre>
    * @param  {Object}   options 옵션
    * <pre>
    * domain  : {String}   URL의 메인도메인 (기본값 : rootContext, 외부링크로 전송하고 싶을 경우 해당 항목을 변경한다.)
    * func    : {Function} 비동기 방식일 때 호출할 CallBack 함수 (기본값:null)
    * sync    : {Boolean}  동기방식 호출여부 (기본값:false)
    * rest    : {Boolean}  REST 호출여부 (기본값:true)
    * method  : {String}   전송형식 (POST/GET), (기본값:POST)
    * loading : {Boolean}  로딩바 표시여부 (기본값:true)
    * alert   : {Boolean}  서버에서 보내주는 메세지 표시여부 (기본값:true)
    * timeout : {Number}   호출 대기시간 (기본값 : su.c.AJAX.timeout 설정값 )
    * focus   : {Boolean}  송신 후 버튼 Focus 복원 (기본값:true)
    * </pre>
    * @return {Object} 동기방식 호출시 {} 형태로 결과값 리턴
    * @author Hwasu, 2011.03.04
    */
    send : function( url, fn, options ) {

      if( su.str(url).isEmpty() ) return false;
    
      if( su.chk.isTypFn(fn) ) {
        var options = options || {}
        options.func = fn;
      } else {
        var options = fn || {}
      }
    
      if( options.rest == false ) {
        su.window.z_req = su.req.z_req;
        if( options.loading == true ) su.loadingBar.show();
        su.window.openPage( url, {method:2} );
      } else {
        return su.req.z_ajax.send( url, options );
      }
      
    }
    
}

/**
*  메세지 처리 유틸리티
*/
su.msg = {

  z_p : {

    msgPool   : [],
    debugPool : [],
    /**
     *  로그 창의 내용을 모두 지운다.
     */
    clearDebugger : function() {
      $(su.c.ID.DEBUG_TXT).html("");
      try{ console.clear(); } catch(e) {}
    },
    
    /**
     *  로그 창을 숨긴다.
     */
    closeDeubgger : function() {
      $(su.c.ID.DEBUG_PANEL).hide();
    },

    /**
     *  디버그용 창을 만든다.
     * @private
     */
    makeDebugger : function( message ) {
    
      // 패널 생성가능 여부를 확인한다.
      if( !$.isReady ) {
        su.msg.z_p.debugPool.push( message );
        setTimeout( arguments.callee, 50 );
        return false;
      }
    
      if( $(su.c.ID.DEBUG_PANEL).length == 1 ) {
        $(su.c.ID.DEBUG_PANEL).show();
        return true;
      }
    
      // 화면 위치를 조정한다.
      var mL   = su.c.LOGGER.window.width - 200;
      var top  = $(window).height() - su.c.LOGGER.window.height - 10;
      var left = $(window).width()  - su.c.LOGGER.window.width  - 20;
    
      if( mL   < 0 ) mL   = 10;
      if( top  < 0 ) top  = 0;
      if( left < 0 ) left = 0;
    
      var id = su.c.ID.DEBUG_PANEL.replace("#","");
    
      // 디버그 패널을 생성한다.
      $("<div>").attr("id", id ).css({
        "position"   : "fixed",
        "font-size"  : su.c.LOGGER.window.fontSize,
        "color"      : "#FFFFFF",
        "background" : "#000000",
        "border"     : "1px solid",
        "opacity"    : "0.7",
        "width"      : su.c.LOGGER.window.width,
        "height"     : su.c.LOGGER.window.height,
        "white-space": "nowrap",
        "top"        : top,
        "left"       : left
      }).append(
        $("<div>").attr( "id", id + "Handler" ).css( "cursor", "move" )
          .append( "<span style='font-weight:bold;text-decoration:underline;'>Store UI Logger</span>" )
          .append( "<a href='javascript:;' onclick='su.msg.z_p.clearDebugger();' style='color:#FAEB78;margin-left:" + mL + "px;'>clear</a>")
          .append( "<a href='javascript:;' onclick='su.msg.z_p.closeDeubgger();' style='color:#FAEB78;margin-left:10px'>close</a>")
          .append( "<a href='javascript:;' onclick='su.c.LOGGER.dialog = false;su.msg.z_p.closeDeubgger();' style='color:#FAEB78;margin-left:10px'>X</a>")
      ).append(
        $("<div>").attr("id", id + "Desc" )
          .css({
            "overflow" : "scroll",
            "height"   : "100%",
            "line-height" : "10px",
            "color" : "#FFFFFF"
          })
      ).draggable({handle:"#"+id+"Handler"}).resizable().appendTo("body").show();
    
      // 미출력 메세지 출력
      $.each( su.msg.z_p.debugPool || {}, function() {
        $(su.c.ID.DEBUG_TXT).append( this.toString() );
      });
    
      su.msg.z_p.debugPool = null;
    
      return true;
    
    }

  },
  
  /**
  *  메세지코드에 해당하는 문자열을 출력한다.
  * <pre>
  *   su.msg.alert( "리펙토링 프로젝트" );
  *   su.msg.alert( "@는 죽어납니다.", "정화수" );
  *   su.msg.alert( "err.info.001", "hudson", "komaf" );
  * </pre>
  * @param  {String}    msgCd       메세지코드 또는 출력할 메세지
  * @param  {Argument}  replaceStr  메세지에서 '@' 문자와 치환될 문자배열
  * @author 정화수. 2011.03.23
  */
  alert : function( msgCd ) {
    alert( su.msg.get(arguments) );
  },
  
  /**
  *  문자열을 confirm box로 보여준 후 사용자의 선택결과를 반환한다.
  * <pre>
  *   if( su.msg.confirm( "ok??" ) ) processY(); else processN();
  * </pre>
  * @param  {String}    msgCd       메세지코드 또는 출력할 메세지
  * @param  {Argument}  param       메세지에서 '@' 문자와 치환될 문자배열
  * @return {Boolean}   true(Y)/false(N)
  */
  confirm : function( msgCd ) {
    return confirm( su.msg.get(arguments) );
  },
  
  /**
   *  로그를 출력한다.
   * @param {String} message 출력할 메세지
   */
  debug : function( message ) {
  
    if( ! su.c.LOGGER.print ) return;
    
    try{
      console.log( su.chk.isTypObj(message) ? message : su.msg.get(arguments) );
    } catch( e ) {}
  
    if( ! su.c.LOGGER.dialog ) return;
    var msg = su.chk.isTypObj(message) ? JSON.stringify(message) : su.msg.get(arguments);

    msg = "<p>" + su.date().toString("[HH:MI:SS]") + " " + su.str(msg).clearXss().val() + "</p>";
  
    if( !su.msg.z_p.makeDebugger(msg) ) return;
  
    $(su.c.ID.DEBUG_TXT).append( msg ).scrollTop( 65535 );
    $(su.c.ID.DEBUG_PANEL).css( "z-index", 65535 );
  
  },
  
  /**
  *  '@' 문자에 해당하는 메세지를 치환해서 반환한다.
  * <pre>
  * su.msg.get( '@_@_TEST', 'merong', 'nuna' ); -> 반활할 문자는 'merong_nuna_TEST'
  * su.msg.get( 'com.alt.001' ); -> 'com.alt.001' 코드에 해당하는 문자를 반환 (코드는 서버와 동일)
  * </pre>
  * @param  {String}    inputMsg  출력할 메세지
  * @param  {Argument}  param     메세지에서 '@' 문자와 치환될 문자열
  * @return {String}  반환할 메세지
  * @author 정화수(2007.07.08)
  */
  get : function( inputMsg ) {
  
    if( su.chk.isTypObj(arguments[0]) ) arguments = arguments[ 0 ];
  
    var argLen = arguments.length;
  
    if( argLen == 0 ) return '';
  
    var code = arguments[0] + "";
  
    var msg = su.msg.z_p.msgPool[ code ];
    if( msg == null ) msg = code;
  
    if( argLen == 1 ) return msg;
  
    // bind @ to Input String (array)
    var idx = 0;
    
    for( var i = 1; i < argLen; i++ ) {
  
      idx = msg.indexOf( "@", idx );
  
      if( idx < 0 ) break;
  
      // "/@" 패턴일 경우 @문자를 그대로 출력
      if( msg.charAt(idx - 1) == "/" ) {
  
        msg = msg.substring( 0, idx - 1 ) + msg.substring( idx );
        i--;
        idx++;
        continue;
      }
  
      var param = su.str( arguments[i] );
  
      var msgAfter = msg.substring( idx + 1 );
  
      // 한글문자일 경우 은/는/이/가 처리
      var charKr = param.getLastKr();
      if( charKr[0] != null ) {
  
        msgAfter = msgAfter.substring( 0, 4 )
          .replace( su.c.REG_PTN.HAN_JOSA_MERGE_1, su.c.REG_PTN.HAN_JOSA[0] )
          .replace( su.c.REG_PTN.HAN_JOSA_MERGE_2, su.c.REG_PTN.HAN_JOSA[2] )
          .replace( su.c.REG_PTN.HAN_JOSA_MERGE_3, su.c.REG_PTN.HAN_JOSA[4] )
          + msgAfter.substring( 4 );
  
        var josa = msgAfter.charAt( 0 );
        var which;
  
        $.each( su.c.REG_PTN.HAN_JOSA, function(n) {
  
          if( josa != this ) return true;
  
          which = n;
          return false;
  
        });
  
        switch( which ) {
  
          case 0 : case 1 :
            which = ( charKr[2] != "" ) ? 0 : 1; break;
          case 2 : case 3 :
            which = ( charKr[2] != "" ) ? 2 : 3; break;
          case 4 : case 5 :
            which = ( charKr[2] != "" ) ? 4 : 5; break;
  
        }
  
        if( which != undefined ) msgAfter = su.c.REG_PTN.HAN_JOSA[which] + msgAfter.substring( 1 );
  
      }
  
      msg = msg.substr( 0, idx ) + param.val() + msgAfter;
      idx = idx + param.length();
    }
  
    return msg;
  
  }

}

/**
 *  콤보박스 클래스.
 * <pre>
 * var combo1 = su.combo("#cmbTemp1");
 * var combo2 = su.combo("#cmbTemp2", "A");
 * </pre>
 * @param {String} id 개체 id
 * @param {String} defaultValue ( A:전체('%'), S:선택(''), null:기본값이 없다. )
 * @return {su.combo}
 * @classDescription 콤보박스 클래스
 */
su.combo = function( id, defaultValue ) {

  var z_self = arguments.callee;
  if(!(this instanceof z_self)) return new z_self( id, defaultValue );

  this.z_id    = id;
  this.z_dfVal = defaultValue;

  // initializing
  if( $.isReady ) {
    this.init();
  } else {
    $(document).ready( $.proxy(this, "init") );
  }

}

su.combo.prototype = {

  /**
   *  콤보박스를 초기화한다.
   * @return {su.combo}
   */
  init : function() {

    if( $(this.z_id).length != 1 ) return this;

    var tagName = $(this.z_id).attr("tagName");

    if( su.chk.isNull(tagName) || tagName != "SELECT" ) {
      su.window.changeTagName( this.z_id, "select" );
    }

    this.setDefault( this.z_dfVal );

    return this;

  },

  /**
   *  콤보의 초기값 설정을 변경한다.
   * @param {String} flag ( A:전체('%'), S:선택(''), null:기본값이 없다. )
   * @return {su.combo}
   */
  setDefault : function( flag ) {

    if( flag != "A" && flag != "S" && ! su.chk.isEmpty(flag) ) return;

    this.z_dfVal = flag;

    var value = $(this.z_id+">option:eq(0)").val();
    var label = $(this.z_id+">option:eq(0)").text();

    var dfVal1 = ( value == "%" && label == su.c.COMBO.LABEL_ALL );
    var dfVal2 = ( value == ""  && label == su.c.COMBO.LABEL_SELECT );

    switch( flag ) {
      // 설정값 변경이 없다면
      case value : break;
      // 설정값을 지우려면
      case undefined : case null : case "" :
        if( !dfVal1 && !dfVal2 ) break;
        $(this.z_id+">option:eq(0)").remove();
        break;
      // 설정값을 변경하려면
      default    :
        if( !dfVal1 && !dfVal2 ) {
          $(this.z_id).prepend( "<option>");
        }
        $(this.z_id+">option:eq(0)").val( flag=="A" ? "%" : "" );
        $(this.z_id+">option:eq(0)").text( flag=="A" ? su.c.COMBO.LABEL_ALL : su.c.COMBO.LABEL_SELECT );
        this.val( flag );
    }

    return this;

  },

  /**
   *  콤보 데이터를 초기화한다.
   * @return {su.combo}
   */
  clear : function() {
    if( $(this.z_id).length == 1 ) {
      $(this.z_id)[0].options.length = 0;
      this.setDefault( this.z_dfVal );
    }

    return this;
  },

  /**
   *  콤보객체의 값을 가져오거나 세팅한다.
   * @param {String} value  세팅할 값
   * @return {String} 객체의 값
   */
  val : function( value ) {
    if( value == undefined ) return $(this.z_id).val();
    $(this.z_id).val( value );
    return value;
  },

  /**
   *  콤보에 데이터를 bind 한다.
   * @param {JSON} data JSON 데이터
   * <pre>
   * 입력되는 데이터는 label, value 속성을 가지고 있어야 한다.
   * 데이터 형식은 다음과 같다.
   * var tempData = [
   *  {value:'1', label:'a-1' },
   *  {value:'2', label:'a-2' }
   * ]
   * </pre>
   * @param {Boolean} appendYn 데이터 추가여부 (기본값:false)
   * @return {su.combo}
   */
  bind : function( data, appendYn ) {

    if( appendYn != true ) this.clear();

    try {
      for (var inx = 0, inxCnt = data.length; inx < inxCnt; inx++) {
        this.add( data[inx].value, su.str(data[inx].label).unclearXss().val() );
      }
    } catch (e) {}

    return this;
  },

  /**
   *  콤보객체에 option 데이터를 추가한다.
   * @param {String} value  값
   * @param {String} label  라벨
   * @return {su.combo}
   */
  add : function( value, label ) {
    var option = new Option( label, value );
    try {
      if( $.browser.msie ) {
        $(this.z_id)[0].add( option );
      } else {
        $(this.z_id)[0].add( option, null );
      }
    } catch( e ) {
      su.msg.debug( e );
    }

    return this;
  },

  /**
   *  콤보객체의 option 데이터를 삭제한다. <br>
   * 일반적으로는 value값만 지정해서 삭제하면 된다.<br>
   * 만약 value 값이 동일한 다건의 option 중 특정 label을 갖는 요소만 삭제하고 싶은 경우에는
   * value와 label을 동시에 지정해준다.
   * @param {String} value 값
   * @param {String} label 라벨 (Optional)
   * @return {su.combo}
   */
  del : function( value, label ) {

    var option = $(this.z_id).find("option[value=" + value + "]");

    if( !su.chk.isNull(label) ) {
      option.each( function() {
        if( $(this).html() == label ) $(this).remove();
      });
    } else {
      option.remove();
    }

    return this;

  }

}


/**
 *  기타 유틸리티
 * @classDescription 기타 유틸리티
 * @author 정화수, 2011.03.15
 */
su.etc = {

  z_seq      : [],
  z_seq_base : new Date() - 0,
  
  /**
   * 배열의 차원을 구한다.
   *
   * @param {Array} array
   * @return {Number} 배열의 차원
   */
  getArrDim : function( array, cnt ) {

    if( su.chk.isNull(cnt) ) cnt = 0;
    if( ! su.chk.isTypArr(array) ) return cnt;

    cnt++;

    try{
      return su.etc.getArrDim( array[0], cnt );
    } catch(e) {
      return cnt;
    }

  },

  /**
   * Object의 key 개수를 구한다.
   * @param {Object} object 데이터 객체
   * @return {Number} Object의 key 개수
   */
  getKeySize : function( object ) {
    return su.etc.getKey(object).length;
  },

  /**
   * Object 의 key 리스트를 구한다.
   *
   * @param {Object} object 데이터 객체
   * @return {Array} key 리스트
   */
  getKey : function( object ) {

    var keys = [];

    for( var key in object ) {
      keys.push( key );
    }

    return keys;

  },

  /**
   * 특정 KEY에 해당하는 일련번호를 채번한다.
   *
   * <pre>
   * su.etc.getSequence( "key" ); -> key_0
   * su.etc.getSequence( "key" ); -> key_1
   * </pre>
   *
   * @param {String} key 일련번호를 채번하기 위한 기준 KEY
   * @return {String} 일련번호 ( key + 채번된 숫자 )
   */
  getSequence : function( key ) {

    if( su.chk.isEmpty(key) ) throw new su.etc.exception( "There is no key to create sequence." );

    if( su.chk.isNull(su.etc.z_seq[key]) ) {
      su.etc.z_seq[key] = 0;
    } else {
      su.etc.z_seq[key] ++;
    }

    return key + "_" +  su.etc.z_seq_base + "_" + su.etc.z_seq[key];

  },

  /**
   *  프레임워크에서 사용하는 사용자 Exception.
   *
   * <pre>
   * throw new su.ect.exceptioin("exception");
   * </pre>
   *
   * @param {String} message 에러메세지
   */
  exception : function( message ) {
    this.name    = "su.EXCEPTION";
    this.message = su.msg.get(arguments);
    su.msg.debug( this.message );
  }

}

/**
 *  윈도우 창 컨트롤에 관련된 유틸리티 클래스
 * @classDescription 윈도우 창 컨트롤에 관련된 유틸리티 클래스
 * @author 정화수, 2011.03.18
 */
su.window = {

  z_idSeq : 0,          // 오브젝트에 부여하는 ID
  z_req   : su.mdata(), // 화면에 넘겨주는 데이터가 저장되는 공간
  z_loginPopup : null,  // 로그인 팝업창 핸들러가 저장되는 공간
  
  /**
  *  팝업을 호출한다.<br>
  * <pre>
  * var popup = su.window.openPopup( "", 200, 300 );
  *
  * 1. 팝업창의 입력상자에 접근해 값을 변경
  * $("#inputPopup", popup.document ).val( "merong" );
  * $( popup.document ).find("#inputPopup").val("merong");
  *
  * 2. 팝업창의 스크립트를 실행
  * $(popup)[0].fnPopupScript();
  *
  * 3. 팝업창에서 부모창에 접근해 값을 변경
  * $("#inputOpner", opner.document).val("merong");
  * </pre>
  *
  * @param {String} url    호출할 페이지
  * @param {Number} width  창 너비
  * @param {Number} height 창 높이
  * @param {Object} option 옵션
  * <pre>
  * scrollbars  : {Boolean} 스크롤바 생성여부 (기본값 : false )
  * status      : {Boolean} 상태바 생성여부 (기본값 : false )
  * toolbar     : {Boolean} 툴바 생성여부 (기본값 : false )
  * copyhistory : {Boolean} ? (기본값 : false )
  * menubar     : {Boolean} 메뉴바 생성여부 (기본값 : false )
  * location    : {Boolean} 주소입력창 생성여부 (기본값 : false )
  * directories : {Boolean} ? (기본값 : false )
  * method      : {String}  팝업화면에 데이터를 보내는 방법 (POST (기본값) / GET)
  * domain      : {String}  URL의 메인도메인 (외부링크를 열고 싶을 경우 해당 항목을 "" 처리한 후 full-URL을 입력한다.)
  * </pre>
  * @exception 팝업차단으로 창이 열리지 않을 때
  * @author 정화수, 2011.03.18
  * @return {Object} 팝업윈도우 핸들러 (팝업이 차단되어 있으면 null을 반환)
  */
  openPopup : function( url, width, height, option ) {

    if( su.chk.isEmpty(url) ) return;

    var op = $.extend({
      scrollbars  : false,
      status      : false,
      resizable   : true, // 크기조절을 막는 기능은 IE에서만 된다.
      toolbar     : false,
      copyhistory : false,
      menubar     : false,
      location    : false,
      directories : false,
      method      : "post",
      domain      : su.c.ROOT_PATH
    }, option || {} );

    op.innerWidth  = Number( width  );
    op.innerHeight = Number( height );

    op.width  = op.innerWidth + 8;
    op.height = op.innerHeight;

    if( ! $.browser.msie ) {
      op.location  = false;
      op.resizable = true;

    } else {
      if( op.location == true ) op.height += 32;
      if( op.toolbar  == true ) op.height += 32;
    }

    var curPos = su.window.getPosition();

    op.top  = curPos.top  + ( (curPos.innerHeight/2) - (op.innerHeight/2) );
    op.left = curPos.left + ( (curPos.innerWidth /2) - (op.innerWidth /2) );

    if( op.scrollbars == true ) {
      op.width += 25;
    }

    var param = "";
    var temp = "";
    for( var c in op ) {
      if( c == "domain" ) continue;
      switch( op[c] ) {
        case true  : temp = "yes"; break;
        case false : temp = "no";  break;
        default    : temp = op[c];
      }
      param += ( c + "=" + temp + "," );
    }
    param = param.replace( /,$/, "" );

    var id = su.etc.getSequence( "openPopup" )

    var popup = window.open( "", id, param );

    if( su.window.isPopupBlocked(popup) ) {
      // 팝업이 차단되어 있다면 에러 표시
      su.msg.alert( "su.api.window.err001" );
      throw new su.etc.exception( "su.api.window.err001" );

    } else {
      var form = su.window.getForm( su.window.z_req );
      form.attr({
        "method" : "post",
        "target" : id,
        "action" : op.domain + url
      }).submit();
      su.window.clearParam();
    }

    return popup;

  },

  /**
  *  현재 화면에 페이지를 연다. (페이지를 이동한다.)
  *
  * @param {String} url  페이지 주소
  * @param {Object} option 옵션
  * <pre>
  * method  : {Number} 1.새 탭에 페이지 로딩, 2.현재 창에 페이지 로딩, 3.리로딩
  * target  : {String} 페이지가 전송될 타겟윈도우ID
  * domain  : {String} URL의 메인도메인 (외부링크를 열고 싶을 경우 해당 항목을 "" 처리한다.)
  * </pre>
  * @exception 팝업차단으로 창이 열리지 않을 때
  * @author 정화수, 2011.03.19
  */
  openPage : function( url, option ) {

    if( su.chk.isEmpty(url) ) return;

    var op = $.extend({
      method  : 1,
      domain  : su.c.ROOT_PATH
    }, option || {} );

    switch( op.method ) {
      case 1 : // new tab

        var id = su.etc.getSequence( "openPage" );
        var newTab = window.open( "", id );

        // 팝업이 차단되어 있다면 에러 표시
        if( su.window.isPopupBlocked(newTab) ) {
          su.msg.alert( "su.api.window.err001" );

          su.msg.debug( ">> newTab.location : " + newTab.location );

          throw new su.etc.exception( "su.api.window.err001" );

        } else {
          var form = su.window.getForm( su.window.z_req );
          form.attr({
            "method" : "post",
            "target" : id,
            "action" : op.domain + url
          }).submit();

        }

        break;

      case 2 : // self
        var form = su.window.getForm( su.window.z_req );
        form.attr({
          "method" : "post",
          "target" : "_self",
          "action" : op.domain + url
        }).submit();
        break;

      case 3 :  // reload
        document.location.reload();
        break;
    }

    su.window.clearParam();

  },

  /**
   * 현재 윈도우 창을 닫는다.
   *
   * <pre>
   * firefox에서는 설정때문에 자동으로 닫기지 않을 수 있다.
   * about:config 에서
   * dom.allow_scripts_to_close_windows 항목이 true 인지를 검사해 보아야 한다.
   * </pre>
   */
  close : function() {

    if( su.detect.isIE || su.detect.isChrome ) {
      window.open('about:blank','_self').close();

    } else if( su.detect.isFireFox ) {
      window.self.close();

    } else {
      window.self.close();
    }

  },

  /**
  *  화면에 넘겨주기 위한 파라미터를 추가한다.
  * @param {String} key 키
  * @param {String} value 값
  */
  addParam : function( key, value ) {
    su.window.z_req.add( key, value );
  },

  /**
  *  화면에 넘겨주기 위한 FORM 파라미터를 추가한다.
  * @param {String} selector  셀럭터
  */
  addForm : function( selector ) {
   su.window.z_req.addForm( selector );
  },

  /**
   *  세팅한 파라미터를 초기화한다.
   */
  clearParam : function() {
    su.window.z_req.clear();
  },

  /**
  *  데이터 전송을 위한 form 객체를 가져온다.
  * @param {su.mdata} mdata  form에 담을 데이터
  * @author 정화수, 2011.3.21
  */
  getForm : function( mdata ) {

    // 폼을 가져온다. (없으면 하나 만든다.)
    var form = $(su.c.ID.WIN_FORM);
    if( form.length == 0 ) {
      form = $("<form>").attr({
        id : su.c.ID.WIN_FORM.replace("#",""),
        "accept-charset" : su.window.getCharset()
      }).appendTo("body");
    }

    // 폼 데이터를 초기화시킨다.
    form.children().remove();

    // 데이터가 없다면 메서드를 종료한다.
    if( su.chk.isNull(mdata) ) return form;

    // 전송할 데이터셋을 만든다.
    var hd  = mdata.getHeader();

    for( var i = 0, icnt = hd.length; i < icnt; i++ ) {
      for( var j = 0, jcnt = mdata.size(); j < jcnt; j ++ ) {
        $("<input>").attr({
          type  : "hidden",
          name  : hd[i],
          value : mdata.get(hd[i],j)
        }).appendTo(form);
      }
    }

    return form;

  },

  /**
   *  툴팁을 붙인다.
   * <pre>
   * su.window.showTip( "툴팁에 보여줄 텍스트", {id:"idInput", top:10, left:20} );
   * </pre>
   * @param {String} text    툴팁에 보여줄 텍스트
   * @param {Object} option  옵션
   * <pre>
   * id   : {String} 툴팁을 붙일 개체 id
   * top  : {Number} 툴팁이 위치할 top  좌표
   * left : {Number} 툴팁이 위치할 left 좌표
   * </pre>
   */
  showTip : function( text, option ) {

    var op = $.extend({
      id    : "",
      top   :  0,
      left  :  0,
      width :  0,
      height : 0
    }, option || {} );

    // top, left 를 설정한다.
    if( $("#"+op.id).length == 1 ) {
      var p = $("#"+op.id).offset();
      op.top    = p.top;
      op.width  = $("#"+op.id).width();
      op.left   = p.left + op.width + 7;
      op.height = $("#"+op.id).height();
    }

    // TIP 객체를 생성한다.
    var tip = $(su.c.ID.WIN_TIP );
    if( tip.length == 0 ) {
      tip = $("<div>").addClass(su.c.CLASS.TIP).attr("id", su.c.ID.WIN_TIP.substr(1)).appendTo("body");
    }

    // 출력내용을 설정한다.
    if( su.chk.isTypArr(text) ) {
      var _msg = "";
      $.each( text, function() {
        _msg += this + "<br />";
      });
      text = _msg;
    }

    // TIP을 설정한다.
    tip.html( text ).css({
      "top"   : op.top,
      "left"  : op.left
    }).show();

  },

  /**
   *  툴팁을 지운다.
   */
  hideTip : function() {
    $(su.c.ID.WIN_TIP ).hide();
  },

  /**
   *  화면에 부여할 ID를 구한다.<br>
   * ID는 한 화면에서 순차적으로 부여된다.
   * @param {Object} obj
   */
  getId : function( obj ) {

    if( $(obj).length == 0 ) return "";

    var id = $(obj).attr("id");

    if( id == "" ) {

      su.window.z_idSeq ++;

      id = su.c.WINDOW.ID_SEQ + su.window.z_idSeq;

      $(obj).attr( "id", id );
    }

    return id;

  },

  /**
   *  화면 URL을 가져온다.
   */
  getUrl : function() {
    return window.location.href;
  },

  /**
   *  화면 URL을 파라미터 정보를 포함하여 다시 세팅한다.
   * @param {su.data} param
   */
  setUrlParam : function( param ) {
    parent.location.hash = param.serialize();
  },

  /**
   *  GET 방식으로 날아온 URL 주소로부터 파라미터 정보를 추출한다.
   *
   * @return {su.data} 파라미터 정보
   */
  getUrlParam : function() {

    var result = su.data();

    var url = this.getUrl();

    var idx = url.indexOf("?");

    if( idx < 0 ) {
      idx = url.indexOf("#");
      if( idx < 0 ) return result;
    }

    url = url.substr( idx + 1 ).replace( "#", "&" );

    var params = url.split( "&" );

    for( var i = params.length - 1; i >= 0; i-- ) {

      var p = su.str( params[i] ).split( "=" );

      result.add( p[0], p[1] || "" );

    }

    return result;

  },

  /**
   *  현재 창이 팝업으로 열렸는지 확인한다.
   * @return {Boolean}
   * @author 정화수, 2011.04.19
   */
  isPopup : function() {
    return ( opener != null && opener.document != null );
  },

  /**
   *  현재 창의 문자셋을 가져온다.
   * @return {String} 문자셋(대문자)
   * @author 정화수, 2011.03.21
   */
  getCharset : function() {
    return ( document.charset || document.characterSet || document.defaultCharset || su.c.CHAR_SET ).toUpperCase();
  },

  /**
   *  현재 포커스된 개체의 ID 를 구한다.
   * @return {String} focus 개체의 ID (focus가 BODY에 있을 경우 "" 을 리턴)
   */
  getFocusId : function() {

    if( $(document.activeElement).attr("tagName") == "BODY" ) return "";

    return su.window.getId( document.activeElement );

  },

  /**
   *  조회결과를 같은 name 속성을 가진 개체의 값으로 binding 한다.
   *
   * <pre>
   * [html]
   * &#60;form id='someForm'&#62;
   *   &#60;input type='text' name='job' /&#62;
   *   &#60;input type='text' name='age' /&#62;
   * &#60;/form>
   *
   * [Java Script]
   * var data = {job:teacher, age:21}
   *
   * su.window.bindByName( "#someForm", data );
   *
   * [기능설명]
   * 셀럭터로 선택한 노드(및 하위 노드)의 name 속성이 데이터의 name 속성과 동일한 경우
   * 노드의 value 값을 데이터의 value 값으로 치환한다.
   * </pre>
   *
   * @param {String} selector 셀렉터
   * @param {Object} data 데이터
   */
  bindByName : function( selector, data ) {

    var self = this;

    $(selector).each( function() {

      var th   = $(this);
      var name = th.attr("name");

      if( !su.chk.isEmpty(name) ) {

        if( th.is(":input") ) {
          th.val( su.str(data[name]).unclearXss().val() );
        } else {
          th.html( su.str(data[name]).unclearXss().val() );
        }

      } else {
        self.bindByName( $("*[name]", th), data );
      }

    });

  },

  /**
   *  조회결과를 같은 id 속성을 가진 개체의 값으로 binding 한다.
   *
   * <pre>
   * [html]
   * &#60;form id='someForm'&#62;
   *   &#60;input type='text' id='job' /&#62;
   *   &#60;input type='text' id='age' /&#62;
   * &#60;/form>
   *
   * [Java Script]
   * var data = {job:teacher, age:21}
   *
   * su.window.bindByName( "#someForm", data );
   *
   * [기능설명]
   * 셀럭터로 선택한 노드(및 하위 노드)의 id 속성이 데이터의 name 속성과 동일한 경우
   * 노드의 value 값을 데이터의 value 값으로 치환한다.
   * </pre>
   *
   * @param {String} selector 셀렉터
   * @param {Object} data 데이터
   */
  bindById : function( selector, data ) {

    var self = this;

    $(selector).each( function() {

      var th   = $(this);
      var id = th.attr("id");

      if( !su.chk.isEmpty(id) ) {

        if( th.is(":input") ) {
          th.val( su.str(data[id]).unclearXss().val() );
        } else {
          th.html( su.str(data[id]).unclearXss().val() );
        }

      } else {
        self.bindById( $("*[id]", th), data );
      }

    });

  },

  /**
   * DOM 엘리먼트의 tag name을 변경한다.<br/>
   * <INPUT> 태그를 <DIV> 태그로 바꾸는 등의 작업에 사용한다.
   *
   * @param {String} id       개체의 ID
   * @param {String} tagName  변경할 tag name (input, div, select ... )
   */
  changeTagName : function( id, tagName ) {

    if( $(id).length != 1 ) return;

    var z_tagName = $(id).attr("tagName");

    if( z_tagName == su.str(tagName).toUpperCase().val() ) return;

    var z_srcTag = $("<div/>").append( $(id).clone(true) ).html();

    var z_trgTag = z_srcTag.replace( eval( "/<" + z_tagName + "/i" ), "<" + tagName ).replace( eval( "/\/" + z_tagName + ">/i" ),"/" + tagName + ">");

    $(id).replaceWith( z_trgTag );

  },

  /**
   *  윈도우의 타이틀 속성을 가져오거나 세팅한다.
   *
   * <pre>
   * su.window.title( "Merong" ); -> 윈도우 창의 타이틀이 'Merong' 으로 바뀐다.
   * su.window.titie(); -> 세팅된 윈도우 창의 타이틀 내용을 가져온다.
   * </pre>
   *
   * @param {String} string
   * @return {String} 윈도우의 title 내용
   */
  title : function( string ) {

    if( su.chk.isNull(string) ) return $(document).attr('title');
    $(document).attr( 'title', string );

  },

  /**
   *  버튼의 활성화 상태를 세팅한다.
   *
   * <pre>
   * su.window.enableBtn( '#btnSave',true  ); -> id가 'btnSave'인 버튼을 활성화 시킨다.
   * su.window.enableBtn( '#btnSave',false ); -> id가 'btnSave'인 버튼을 비활성화 시킨다.
   * </pre>
   *
   * @param {String} selector 셀렉터
   * @param {Boolean} enableYn 활성화 여부
   */
  enableBtn : function( selector, enableYn ) {

    if( enableYn == true ) {
      $(selector).removeAttr( "disabled" );

    } else {
      $(selector).attr( "disabled", "true" );
    }

  },

  /**
   * 브라우저 창의 속성정보를 가져온다.
   *
   * <pre>
   * 1. IE 계열은 width, heigth 정보가 정확하지 않는다.
   * </pre>
   *
   * @return {Object} 속성정보 {top,left,width,height,innerWidth,innerHeight}
   */
  getPosition : function() {

    var result = {}

    if( $.browser.msie ) {

      var doc = document.documentElement || document.body;

      result.top         = window.screenTop;
      result.left        = window.screenLeft;
      result.width       = doc.clientWidth + 8;
      result.height      = doc.clientHeight + 220;
      result.innerWidth  = doc.clientWidth;
      result.innerHeight = doc.clientHeight;

    } else {
      result.top         = window.screenY;
      result.left        = window.screenX;
      result.width       = window.outerWidth;
      result.height      = window.outerHeight;
      result.innerWidth  = window.innerWidth;
      result.innerHeight = window.innerHeight;
    }

    return result;

  },

  /**
   * 팝업창이 차단되었는지 여부를 확인한다.
   *
   * <pre>
   * var newTab = su.window.openPopup( "http://...", "popupWindowId" );
   *
   * su.window.isPopupBlocked( newTab ); -> 팝업창 차단여부를 돌려줌
   *
   * ※ 구글 크롬에서는 정상적으로 작동하지 않는다.
   * </pre>
   *
   * @param {Object} popupWindow
   * @return {Boolean} 팝업창 차단여부
   */
  isPopupBlocked : function( popupWindow ) {

    if( !popupWindow|| popupWindow.closed || typeof popupWindow.closed=='undefined' ) return true;

    return false;

  },

  /**
   * 로그인 팝업 화면을 호출한다.
   * @private
   */
  z_openLoginPopup : function() {

    if( su.window.z_loginPopup != null && su.window.z_loginPopup.closed ) {
      su.window.z_loginPopup = null;
    }

    if( su.window.z_loginPopup == null ) {
      su.window.z_loginPopup = su.window.openPopup( su.c.URL_NO_SESSION, 290, 215 );
    }

  }

}

/**
 * 쿠키 관련 유틸클래스.
 * 보안상 중요한 정보는 절대로 쿠기에 저장해서는 안된다.
 */
su.cookie = {

  /**
   *  쿠키지원여부를 확인한다.
   * @return {Boolean}
   */
  isSupported : function() {
  
    var result;
  
    if( $.browser.msie ) {
  
      var chkKey = "_duChkCookieEnabled";
      var chkVal = Math.round( Math.random() * 10000 ) + "";
  
      su.cookie.set( chkKey, chkVal, {} );
      result = ( su.cookie.get(chkKey) == chkVal ) ? true : false;
      su.cookie.del( chkKey );
  
    } else {
  
      result = navigator.cookieEnabled;
  
    }
  
    return result;
  
  },
  
  /**
   *  쿠키를 설정한다.
   * <pre>
   * su.cookie.set( "key", "1" ); -> 현재 디렉토리부터 사용할 쿠키 세팅
   * su.cookie.set( "key", "1", { path:"/pls/pls000/"} ); -> 특정 디렉토리부터 사용할 쿠키를 세팅
   * su.cookie.set( "key", "1", { path:"/", expires:100} ); -> 전체범위 쿠키 세팅, 만기일은 생성시부터 100일 후
   * </pre>
   * @param {Object} key    키
   * @param {Object} value  값
   * @param {Object} option 옵션
   * <pre>
   * expires : {Number}  만료기간(일자, 기본값:365)
   * path    : {String}  쿠키가 유효하게 사용될 수 있는 URL 디렉토리 (위치는 하위상속됨, 기본값 : 현재위치)
   * secure  : {Boolean} HTTPS 사용여부 (기본값 : false)
   * </pre>
   */
  set : function( key, value, option ) {
  
    var _path = location.pathname;
    _path = _path.substr( 0, _path.lastIndexOf("/") + 1 );
  
    var op = $.extend({
      expires : 365,
      path    : _path,
  //    domain  : "", IE 에서 domain 설정이 들어가면 쿠키값이 저장이 안된다.
      secure  : false
    }, option || {} );
  
    var expires = new Date();
    expires.setDate( expires.getDate() + op.expires );
  
    document.cookie = key + "=" + encodeURIComponent( value )
     + "; expires=" + expires.toUTCString()
     + "; path="    + op.path
     + ( op.secure != false ? "; secure" : "" );
  
  },
  
  /**
   *  쿠키를 삭제한다.
   * <pre>
   * su.cookie.del( "key" ); -> 현재 디렉토리부터 세팅된 쿠키 삭제
   * su.cookie.del( "key", "/pls/pls000/" ); -> 해당 디렉토리부터 세팅된 쿠키를 삭제
   * su.cookie.del( "key", "/" ); -> 전체범위 쿠키 삭제
   * </pre>
   * @param {Object} key  키
   * @param {Object} path 쿠키가 유효하게 사용될 수 있는 URL 디렉토리 (위치는 하위상속됨, 기본값 : 현재위치)
   */
  del : function( key, path ) {
  
    var _path = location.pathname;
    _path = _path.substr( 0, _path.lastIndexOf("/") + 1 );
  
    path = path || _path;
  
    var expires = new Date();
  
    expires.setDate( expires.getDate() - 1 );
  
    document.cookie = key + "=; expires=" + expires.toGMTString() + "; path=" + path;
  
  },
  
  /**
   *  쿠키값을 가져온다.
   * 키가 존재하지 않을 경우 false를 반환받는다.
   * @param {Object} key 키
   * @return {String|Boolean} 값 또는 false
   */
  get : function( key ) {
  
    var result;
  
    if( result = new RegExp(key + "=(.*?)(?:;|$)").exec(document.cookie) ) {
      return decodeURIComponent( result[1] );
    }
  
    return false;
  
  }

}

/**
* 파일처리 클래스
* @author 정화수 2011.05.20
* @classDescription 파일처리 클래스
*
*/
su.file = {
  
  z_req : su.data(),

   /**
   *  파라미터를 추가한다.
   * @param {String} key 키
   * @param {String} value 값
   */
  addParam : function( key, value ) {
    su.file.z_req.add( key, value );
  },

  /**
  * FORM 파라미터를 추가한다.
  * @param {String} expression  셀럭터
  */
  addForm : function( expression ) {
    su.file.z_req.addForm( expression );
  },

  /**
   *  세팅한 파라미터를 초기화한다.
   */
  clearParam : function() {
    su.file.z_req.clear();
  },
  
  /**
   *  서버에 저장된 파일을 내려받는다.
   *
   * <pre>
   * su.file.download( "FRM-000001" ); -> 'FRM000001' 로 저장된 파일을 다운로드한다.
   * </pre>
   *
   * @param {Sting} key 파일 KEY
   * @return {Boolean} 작업성공여부
   */
  download : function( url ) {

    $(su.c.ID.FILE_DN).remove();

    $("<iframe>").attr({
      id  : su.c.ID.FILE_DN.substr(1),
      src : su.c.ROOT_PATH + url + "?" + su.file.z_req.serialize()
    }).hide().appendTo("body");

    return true;

  }

}

/**
*  날짜처리함수<br>
* <pre>
* su.date().val() -> 서버기준 시간을 초기값으로 가지고 있는 신규 Date객체
* su.date(new Date(2011,3-1,5)).val() -> Date객체 (2011-03-05)
* su.date("03-05-2011","MM-DD-YYYY").val() -> Date객체 (2011-03-05)
* </pre>
* @param {String/Date/su.date} date 날짜
* @param {String} format 입력포맷 (기본값 : {@link su.c.DATE.FORMAT} )
* @return {su.date}
* @classDescription 날짜처리함수
* @author 정화수 2011.03.23
*/
su.date = function( date, format ) {

  var z_self = arguments.callee;

  if(!(this instanceof z_self)) return new z_self( date, format );

  this.z_format = format || su.c.DATE.FORMAT;

  if( su.chk.isEmpty(date) ) {
    this.val( this.getNow(), this.z_format );
  } else {
    this.val( date, this.z_format );
  }

}

su.date.prototype = {

  /**
  *  Date 객체를 가져온다.<br>
  * 처리에 필요한 Date 객체를 세팅할 수도 있다.
  * 잘못된 정보로 Date 객체가 세팅될 경우 null이 저장된다.
  * <pre>
  * su.date().val() -> Date객체(신규)
  * su.date().val(new Date(2011,3-1,5)).val() -> Date객체 (2011-03-05)
  * su.date().val("03-05-2011","MM-DD-YYYY").val() -> Date객체 (2011-03-05)
  * </pre>
  * @param {String,Date} date 날짜
  * @param {String} format 입력포맷 (기본값:YYYY-MM-DD, YYYY,MM,DD,HH,MI,SS조합)
  * @return {Date,su.date}
  */
  val : function( date, format ) {

    if( arguments.length == 0 ) return this.z_val;

    if( su.chk.isTypDate(date) ) {
      this.z_val = date;
      return this;
    } else if( date instanceof su.date ) {
      this.z_val = date.val();
      return this;
    }

    // get dateArray through format
    var z_dtArr = su.str(date).getDateArray( format || this.z_format );

    $(z_dtArr).each(function(i){
      if( z_dtArr[i] == null ) return true;
      z_dtArr[i] = Number(z_dtArr[i]);
    });

    // calibrate date
    z_dtArr[1] = (z_dtArr[1]==null) ? null : z_dtArr[1] - 1; // 월데이터 보정
    z_dtArr[2] = (z_dtArr[2]==null) ? 1 : z_dtArr[2]; // 일데이터 보정

    // create Date object
    var z_date = new Date( z_dtArr[0], z_dtArr[1], z_dtArr[2], z_dtArr[3], z_dtArr[4], z_dtArr[5], 1 );

    // check validation
    var z_chk  = [];
    z_chk.push( "" + z_date.getFullYear() );
    z_chk.push( "" + z_date.getMonth() );
    z_chk.push( "" + z_date.getDate() );
    z_chk.push( "" + z_date.getHours() );
    z_chk.push( "" + z_date.getMinutes() );
    z_chk.push( "" + z_date.getSeconds() );

    var z_flag = true;

    $(z_chk).each(function(i){
      if( z_dtArr[i] != null && z_dtArr[i] != z_chk[i] ) {
        z_flag = false;
        return false;
      }
    });

    // save result
    this.z_val = (z_flag) ? z_date : null;

    return this;

  },

  /**
   *  포맷을 가져오거나 변경한다.
   *
   * <pre>
   *
   * var d = su.date( "2011-01-01" );
   *
   * d.format(); -> "YYYY-MM-DD"
   *
   * d.format( "YYYYMMDD" );
   * d.format(); -> "YYYYMMDD"
   *
   * </pre>
   *
   * @param {Object} format
   * @return {String} 포맷
   */
  format : function ( format ) {

    if( arguments.length == 0 ) return this.z_format;
    this.z_format = su.str( format ).val();

  },

  /**
   *  날짜를 포맷에 맞게 변환한다.
   * @param {String} format 포맷 (기본값:시간데이터 세팅시 입력한 포맷(미입력시 YYYY-MM-DD), YYYY,MM,DD,HH,MI,SS조합)
   * @return {String} 포맷이 적용된 날짜
   */
  toString : function( format ) {

    if( this.z_val == null ) return "";
    
    var z_result = su.str(format||this.z_format).toUpperCase().val();
    var z_date   = this.z_val;

    return su.str(z_result).replace( "YYYY", z_date.getFullYear() )
      .replace( "MM", su.str(z_date.getMonth()+1).lpad(2,"0").val() )
      .replace( "DD", su.str(z_date.getDate()   ).lpad(2,"0").val() )
      .replace( "HH", su.str(z_date.getHours()  ).lpad(2,"0").val() )
      .replace( "MI", su.str(z_date.getMinutes()).lpad(2,"0").val() )
      .replace( "SS", su.str(z_date.getSeconds()).lpad(2,"0").val() ).val();

    return result;

  },

  /**
   *  현재시간 추출 관련 환경 초기화
   * @param {String} date YYYYMMDDHHMISS 형식의 문자열
   */
  initNow : function( date ) {

    if( su.chk.isEmpty(date) ) {
      su.c.DATE.SERVER_DATE = new Date();
    } else {
      su.c.DATE.SERVER_DATE = this.val(date,"YYYYMMDDHHMISS").val();
    }

    su.c.DATE.LOCAL_DATE = new Date();

  },

  /**
   *  현재 시간을 구한다.<br>
   * new Date() 함수는 로컬 PC 의 시간을 구하기 때문에 현재 시간을 구하는 용도로 사용해서는 안된다.
   * @return {Date}
   */
  getNow : function() {

    if( su.c.DATE.SERVER_DATE == null ) this.initNow();

    var gap = new Date() - su.c.DATE.LOCAL_DATE;
    var now = su.c.DATE.SERVER_DATE.valueOf() + gap;

    return new Date( now );

  },

  /**
   *  년도(4자리)를 구한다.
   *
   * @return {Number} 년도
   */
  getYear  : function()  {
    return this.z_val.getFullYear();
  },

  /**
   *  월을 구한다.
   *
   * @return {Number} 월 (1-12)
   */
  getMonth : function()  {
    return this.z_val.getMonth() + 1;
  },

  /**
   *  일을 구한다.
   *
   * @return {Number} 일
   */
  getDate : function()  {
    return this.z_val.getDate();
  },

  /**
   *  시간을 구한다.
   *
   * @return {Number} 시간
   */
  getHour : function() {
    return this.z_val.getHours();
  },

  /**
   *  분을 구한다.
   *
   * @return {Number} 분
   */
  getMin : function() {
    return this.z_val.getMinutes();
  },

  /**
   *  초를 구한다.
   *
   * @return {Number} 초
   */
  getSec : function() {
    return this.z_val.getSeconds();
  },

  /**
   *  요일을 구한다.
   *
   * @return {Number} 요일에 해당하는 숫자 (0:일요일 - 6:토요일)
   */
  getDay   : function()  {
    return this.z_val.getDay();
  },


  /**
   *  두 날짜간 차이를 구한다.
   *
   * <pre>
   * var a = su.date("2011-01-01");
   * var b = su.date("2011-01-03");
   * su.msg.debug( "날짜차이 : " + b.getBetweenDate( a ) ); -> -2일 차이
   * </pre>
   *
   * @param {String/Date/su.date} date
   * @param {String} format
   * @return {Number} 일자
   */
  getBetweenDate : function(date, format) {

    var cDate;

    if( su.chk.isTypDate(date) ) {
      cDate = date;
    } else if( su.chk.isTypStr(date) ) {
      cDate = su.date( date, format );
    } else if( date instanceof su.date ) {
      cDate = date.val();
    }

    return ( cDate - this.val() ) / 1000 / 60 / 60 / 24;

  },

  /**
   *  두 날짜간 시간차이를 구한다.
   *
   * <pre>
   * var a = su.date("2011-01-01");
   * var b = su.date("2011-01-03");
   * su.msg.debug( "시간차이 : " + b.getBetweeHour( a ) ); -> -48시간 차이
   * </pre>
   *
   * @param {String/Date/su.date} date
   * @param {String} format
   * @return {Number} 시간
   */
  getBetweenHour : function(date, format) {

    var cDate = 0;

    if( su.chk.isTypDate(date) ) {
      cDate = date;
    } else if( su.chk.isTypStr(date) ) {
      cDate = su.date( date, format );
    } else if( date instanceof su.date ) {
      cDate = date.val();
    }

    return ( cDate - this.val() ) / 1000 / 60 / 60;

  },

  /**
   *  두 날짜간 분차이를 구한다.
   *
   * <pre>
   * var a = su.date("2011-01-03 12:10");
   * var b = su.date("2011-01-03 12:20");
   * su.msg.debug( "시간차이 : " + b.getBetweeHour( a ) ); -> -10분 차이
   * </pre>
   *
   * @param {String/Date/su.date} date
   * @param {String} format
   * @return {Number} 분
   */
  getBetweenMinute : function(date, format) {

    var cDate = 0;

    if( su.chk.isTypDate(date) ) {
      cDate = date;
    } else if( su.chk.isTypStr(date) ) {
      cDate = su.date( date, format );
    } else if( date instanceof su.date ) {
      cDate = date.val();
    }

    return ( cDate - this.val() ) / 1000 / 60;

  },

  /**
   *  월초로 세팅한다.
   *
   * <pre>
   * su.Date("2011-04-17").setLastDate().toString(); -> "2011-04-30" 반환
   * </pre>
   *
   * @return {su.date}
   */
  setFirstMonDate : function() {
    if( this.z_val != null ) this.z_val = new Date( this.getYear(), this.getMonth() - 1, 1, this.getHour(), this.getMin(), this.getSec(), 1 );
    return this;
  },

  /**
   *  월말로 세팅한다.
   *
   * <pre>
   * su.Date("2011-04-17").setLastDate().toString(); -> "2011-04-30" 반환
   * </pre>
   *
   * @return {su.date}
   */
  setLastMonDate : function() {
    if( this.z_val != null ) this.z_val = new Date( this.getYear(), this.getMonth(), 0, this.getHour(), this.getMin(), this.getSec(), 1 );
    return this;
  },

  /**
   *  년을 더한다.
   *
   * <pre>
   * su.date().addYear( 1  ); -> 현재시간에서 1년을 더한다.
   * su.date().addYear( -1 ); -> 현재시간에서 1년을 뺀다.
   * </pre>
   *
   * @param {Number} num 더할 년
   * @return {su.date}
   */
  addYear : function( num )  {
    if( this.z_val != null ) this.z_val.setFullYear( this.z_val.getFullYear() + num );
    return this;

  },

  /**
   *  월을 더한다.
   *
   * <pre>
   * su.date().addMonth( 1  ); -> 현재시간에서 1달을 더한다.
   * su.date().addMonth( -1 ); -> 현재시간에서 1달을 뺀다.
   * </pre>
   *
   * @param {Number} num 더할 월
   * @return {su.date}
   */
  addMonth : function( num )  {
    if( this.z_val != null ) this.z_val.setMonth( this.z_val.getMonth() + num );
    return this;

  },

  /**
   *  일을 더한다.
   *
   * <pre>
   * su.date().addDate( 1  ); -> 현재시간에서 1일을 더한다.
   * su.date().addDate( -1 ); -> 현재시간에서 1일을 뺀다.
   * </pre>
   *
   * @param {Number} num 더할 일
   * @return {su.date}
   */
  addDate : function( num )  {
    if( this.z_val != null ) this.z_val.setDate( this.z_val.getDate() + num );
    return this;
  },

  /**
   *  시간을 더한다.
   *
   * <pre>
   * su.date().addHour( 1  ); -> 현재시간에서 1시간을 더한다.
   * su.date().addHour( -1 ); -> 현재시간에서 1시간을 뺀다.
   * </pre>
   *
   * @param {Number} num 더할 시간
   * @return {su.date}
   */
  addHour : function( num )  {
    if( this.z_val != null ) this.z_val.setHours( this.z_val.getHours() + num );
    return this;
  },

  /**
   *  분을 더한다.
   *
   * <pre>
   * su.date().addMinutes( 1  ); -> 현재시간에서 1분을 더한다.
   * su.date().addMinutes( -1 ); -> 현재시간에서 1분을 뺀다.
   * </pre>
   *
   * @param {Number} num 더할 시간
   * @return {su.date}
   */
  addMinutes : function( num )  {
    if( this.z_val != null ) this.z_val.setMinutes( this.z_val.getMinutes() + num );
    return this;
  }

}

/**
* 업무처리 유틸리티
* 
* @class 업무처리 유틸리티
* @author 정화수 2014.08.21
*/
su.biz = {

 /**
  * 공통코드 정보를 구한다.
  * 
  * @param {String} cd 검색할 공통코드
  * 
  * @return {Object} 공통코드 {grpCd, dtlCd, dtlFullCd, cdNm, addField1, useYN, description}
  */
  getCmCd : function( cd ) {
  
    su.req.addParam( "cd", cd );
      
    return su.req.send( "/featuredCommon/ajax/getCmCdDtl.omp", {sync:true, loadingBar:false} );
      
  }
  
}

/**
 *  브라우저/OS 탐지를 처리하는 유틸클래스
 * @classDescription 브라우저/OS 탐지를 처리하는 유틸클래스
 * @author Stoimen (modified by 정화수)
 */
su.detect = {

  z_browser : [
    { string: navigator.userAgent, subString:"Chrome",  identity:"Chrome"                             },
    { string: navigator.userAgent, subString:"OmniWeb", identity:"OmniWeb",  versionSearch:"OmniWeb/" },
    { string: navigator.vendor,    subString:"Apple",   identity:"Safari",   versionSearch:"Version"  },
    { prop  : window.opera,                             identity:"Opera",    versionSearch:"Version"  },
    { string: navigator.vendor,    subString:"iCab",    identity:"iCab"                               },
    { string: navigator.vendor,    subString:"KDE",     identity:"Konqueror"                          },
    { string: navigator.userAgent, subString:"Firefox", identity:"Firefox"                            },
    { string: navigator.vendor,    subString:"Camino",  identity:"Camino"                             },
    { string: navigator.userAgent, subString:"MSIE",    identity:"Explorer", versionSearch:"MSIE"     },
    { string: navigator.userAgent, subString:"Gecko",   identity:"Mozilla",  versionSearch:"rv"       },
    { string: navigator.userAgent, subString:"Mozilla", identity:"Netscape", versionSearch:"Mozilla"  }, // for older Netscapes (4-)
    { string: navigator.userAgent, subString:"Netscape",identity:"Netscape"                           }  // for newer Netscapes (6+)
  ],

  z_os : [
    { string: navigator.platform,  subString:"Win",    identity:"Windows"     },
    { string: navigator.platform,  subString:"Mac",    identity:"Mac"         },
    { string: navigator.userAgent, subString:"iPhone", identity:"iPhone/iPod" },
    { string: navigator.platform,  subString:"Linux",  identity:"Linux"       }
  ],

  /**
   * @private
   */
  z_searchString : function( data ) {
    for( var i=0, iCnt = data.length; i < iCnt; i++ ) {
      var dataString = data[i].string;
      var dataProp = data[i].prop;
      this.versionSearchString = data[i].versionSearch || data[i].identity;
      if (dataString) {
        if (dataString.indexOf(data[i].subString) != -1)
        return data[i].identity;
      } else if (dataProp) {
        return data[i].identity;
      }
    }
  },

  /**
   * @private
   */
  z_searchVersion : function( verStr ) {
    var idx = verStr.indexOf( this.versionSearchString );
    if( idx == -1 ) return;
    return parseFloat( verStr.substring(idx + this.versionSearchString.length + 1) );
  }

}

/**
 * 브라우저 종류.
 * @return {String}
 */
su.detect.browser = su.detect.z_searchString( su.detect.z_browser ) || "unknown";

/**
 * 브라우저 버젼.
 * <ul>
 * <li>탐지를 못할 경우 0을 반환한다.</li>
 * <li>IE8은 렌더링 모드에 따라 7 또는 6을 반환할 수 있다.</li>
 * </ul>
 * @return {Number}
 */
su.detect.version = su.detect.z_searchVersion(navigator.userAgent) || su.detect.z_searchVersion(navigator.appVersion) || 0;

/**
 * OS 종류.
 * @return {String}
 */
su.detect.os = su.detect.z_searchString( su.detect.z_os ) || "unknown";

/**
 * OS가 Window 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isWin = ( su.detect.os == "Windows" );

/**
 * OS가 Mac 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isMac = ( su.detect.os == "Mac" );

/**
 * OS가 Linux 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isLinux = ( su.detect.os == "Linux" );

/**
 * OS가 I-Phone 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isIphone = ( su.detect.os == "iPhone" );

/**
 * 브라우저가 Explorer 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isIE = ( su.detect.browser == "Explorer" );

/**
 * 브라우저가 Firefox 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isFireFox = ( su.detect.browser == "Firefox" );

/**
 * 브라우저가 Safari 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isSafari = ( su.detect.browser == "Safari" );

/**
 * 브라우저가 Opera 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isOpera = ( su.detect.browser == "Opera" );

/**
 * 브라우저가 Chrome 인지 여부를 확인한다.
 * @return {Boolean}
 */
su.detect.isChrome = ( su.detect.browser == "Chrome" );

// Setup for removing init setting in [techsupport.common.js]
$.ajaxSetup({
  beforeSend : null,
  success    : null
});